/* Output routines.
   Copyright (C) 1989-2025 Free Software Foundation, Inc.
   Written by Douglas C. Schmidt <schmidt@ics.uci.edu>
   and Bruno Haible <bruno@clisp.org>.

   This file is part of GNU GPERF.

   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 <https://www.gnu.org/licenses/>.  */

/* Specification. */
#include "output.h"

#include <stdio.h>
#include <string.h> /* declares strncpy(), strchr() */
#include <ctype.h>  /* declares isprint() */
#include <assert.h> /* defines assert() */
#include <limits.h> /* defines SCHAR_MAX etc. */
#include "options.h"
#include "version.h"
#include "config.h"

/* ============================== Portability ============================== */

/* Dynamically allocated array with dynamic extent:

   Example:
       DYNAMIC_ARRAY (my_array, int, n);
       ...
       FREE_DYNAMIC_ARRAY (my_array);

   Attention: depending on your implementation my_array is either the array
   itself or a pointer to the array! Always use my_array only as expression!
 */
#if HAVE_DYNAMIC_ARRAY
  #define DYNAMIC_ARRAY(var,eltype,size) eltype var[size]
  #define FREE_DYNAMIC_ARRAY(var)
#else
  #define DYNAMIC_ARRAY(var,eltype,size) eltype *var = new eltype[size]
  #define FREE_DYNAMIC_ARRAY(var) delete[] var
#endif

/* ========================================================================= */

/* The "register " storage-class specifier.  */
static const char *register_scs;

/* The "const " qualifier.  */
static const char *const_always;

/* The "const " qualifier, for read-only arrays.  */
static const char *const_readonly_array;

/* The "const " qualifier, for the array type.  */
static const char *const_for_struct;

/* Returns the smallest unsigned C type capable of holding integers
   up to N.  */

static const char *
smallest_integral_type (int n)
{
  if (n <= UCHAR_MAX) return "unsigned char";
  if (n <= USHRT_MAX) return "unsigned short";
  return "unsigned int";
}

/* Returns the smallest signed C type capable of holding integers
   from MIN to MAX.  */

static const char *
smallest_integral_type (int min, int max)
{
  if (option[ANSIC] | option[CPLUSPLUS])
    if (min >= SCHAR_MIN && max <= SCHAR_MAX) return "signed char";
  if (min >= SHRT_MIN && max <= SHRT_MAX) return "short";
  return "int";
}

/* ------------------------------------------------------------------------- */

/* Constructor.
   Note about the keyword list starting at head:
   - The list is ordered by increasing _hash_value.  This has been achieved
     by Search::sort().
   - Duplicates, i.e. keywords with the same _selchars set, are chained
     through the _duplicate_link pointer.  Only one representative per
     duplicate equivalence class remains on the linear keyword list.
   - Accidental duplicates, i.e. keywords for which the _asso_values[] search
     couldn't achieve different hash values, cannot occur on the linear
     keyword list.  Search::optimize would catch this mistake.
 */
Output::Output (KeywordExt_List *head, const char *struct_decl,
                unsigned int struct_decl_lineno, const char *return_type,
                const char *struct_tag, const char *verbatim_declarations,
                const char *verbatim_declarations_end,
                unsigned int verbatim_declarations_lineno,
                const char *verbatim_code, const char *verbatim_code_end,
                unsigned int verbatim_code_lineno, bool charset_dependent,
                int total_keys, int max_key_len, int min_key_len,
                bool hash_includes_len, const Positions& positions,
                const unsigned int *alpha_inc, int total_duplicates,
                unsigned int alpha_size, const int *asso_values)
  : _head (head), _struct_decl (struct_decl),
    _struct_decl_lineno (struct_decl_lineno), _return_type (return_type),
    _struct_tag (struct_tag),
    _verbatim_declarations (verbatim_declarations),
    _verbatim_declarations_end (verbatim_declarations_end),
    _verbatim_declarations_lineno (verbatim_declarations_lineno),
    _verbatim_code (verbatim_code),
    _verbatim_code_end (verbatim_code_end),
    _verbatim_code_lineno (verbatim_code_lineno),
    _charset_dependent (charset_dependent),
    _total_keys (total_keys),
    _max_key_len (max_key_len), _min_key_len (min_key_len),
    _hash_includes_len (hash_includes_len),
    _key_positions (positions), _alpha_inc (alpha_inc),
    _total_duplicates (total_duplicates), _alpha_size (alpha_size),
    _asso_values (asso_values)
{
}

/* ------------------------------------------------------------------------- */

/* Computes the minimum and maximum hash values, and stores them
   in _min_hash_value and _max_hash_value.  */

void
Output::compute_min_max ()
{
  /* Since the list is already sorted by hash value all we need to do is
     to look at the first and the last element of the list.  */

  _min_hash_value = _head->first()->_hash_value;

  KeywordExt_List *temp;
  for (temp = _head; temp->rest(); temp = temp->rest())
    ;
  _max_hash_value = temp->first()->_hash_value;
}

/* ------------------------------------------------------------------------- */

/* Returns the number of different hash values.  */

int
Output::num_hash_values () const
{
  /* Since the list is already sorted by hash value and doesn't contain
     duplicates, we can simply count the number of keywords on the list.  */
  int count = 0;
  for (KeywordExt_List *temp = _head; temp; temp = temp->rest())
    count++;
  return count;
}

/* -------------------- Output_Constants and subclasses -------------------- */

/* This class outputs an enumeration defining some constants.  */

struct Output_Constants
{
  virtual void          output_start () = 0;
  virtual void          output_item (const char *name, int value) = 0;
  virtual void          output_end () = 0;
                        Output_Constants () {}
  virtual               ~Output_Constants () {}
};

/* This class outputs an enumeration in #define syntax.  */

struct Output_Defines : public Output_Constants
{
  virtual void          output_start ();
  virtual void          output_item (const char *name, int value);
  virtual void          output_end ();
                        Output_Defines () {}
  virtual               ~Output_Defines () {}
};

void Output_Defines::output_start ()
{
  printf ("\n");
}

void Output_Defines::output_item (const char *name, int value)
{
  printf ("#define %s %d\n", name, value);
}

void Output_Defines::output_end ()
{
}

/* This class outputs an enumeration using 'enum'.  */

struct Output_Enum : public Output_Constants
{
  virtual void          output_start ();
  virtual void          output_item (const char *name, int value);
  virtual void          output_end ();
                        Output_Enum (const char *indent)
                          : _indentation (indent) {}
  virtual               ~Output_Enum () {}
private:
  const char *_indentation;
  bool _pending_comma;
};

void Output_Enum::output_start ()
{
  printf ("%senum\n"
          "%s  {\n",
          _indentation, _indentation);
  _pending_comma = false;
}

void Output_Enum::output_item (const char *name, int value)
{
  if (_pending_comma)
    printf (",\n");
  printf ("%s    %s = %d", _indentation, name, value);
  _pending_comma = true;
}

void Output_Enum::output_end ()
{
  if (_pending_comma)
    printf ("\n");
  printf ("%s  };\n\n", _indentation);
}

/* Outputs a constant in the given style.  */

static void
output_constant (struct Output_Constants& style, const char *name, int value)
{
  const char *prefix = option.get_constants_prefix ();
  DYNAMIC_ARRAY (combined_name, char, strlen (prefix) + strlen (name) + 1);
  strcpy (combined_name, prefix);
  strcpy (combined_name + strlen (prefix), name);
  style.output_item (combined_name, value);
  FREE_DYNAMIC_ARRAY (combined_name);
}

/* Outputs the maximum and minimum hash values etc.  */

void
Output::output_constants (struct Output_Constants& style) const
{
  style.output_start ();
  output_constant (style, "TOTAL_KEYWORDS", _total_keys);
  output_constant (style, "MIN_WORD_LENGTH", _min_key_len);
  output_constant (style, "MAX_WORD_LENGTH", _max_key_len);
  output_constant (style, "MIN_HASH_VALUE", _min_hash_value);
  output_constant (style, "MAX_HASH_VALUE", _max_hash_value);
  style.output_end ();
}

/* ------------------------------------------------------------------------- */

/* We use a downcase table because when called repeatedly, the code
       gperf_downcase[c]
   is faster than
       if (c >= 'A' && c <= 'Z')
         c += 'a' - 'A';
 */
#define USE_DOWNCASE_TABLE 1

#if USE_DOWNCASE_TABLE

/* Output gperf's ASCII-downcase table.  */

static void
output_upperlower_table ()
{
  unsigned int c;

  printf ("#ifndef GPERF_DOWNCASE\n"
          "#define GPERF_DOWNCASE 1\n"
          "static %sunsigned char gperf_downcase[256] =\n"
          "  {",
          const_readonly_array);
  for (c = 0; c < 256; c++)
    {
      if ((c % 15) == 0)
        printf ("\n   ");
      printf (" %3d", c >= 'A' && c <= 'Z' ? c + 'a' - 'A' : c);
      if (c < 255)
        printf (",");
    }
  printf ("\n"
          "  };\n"
          "#endif\n\n");
}

#endif

/* Output gperf's ASCII-case insensitive strcmp replacement.  */

static void
output_upperlower_strcmp ()
{
  printf ("#ifndef GPERF_CASE_STRCMP\n"
          "#define GPERF_CASE_STRCMP 1\n"
          "static int\n"
          "gperf_case_strcmp ");
  printf (option[KRC] ?
               "(s1, s2)\n"
          "     %schar *s1;\n"
          "     %schar *s2;\n" :
          option[C] ?
               "(s1, s2)\n"
          "     %sconst char *s1;\n"
          "     %sconst char *s2;\n" :
          option[ANSIC] | option[CPLUSPLUS] ?
               "(%sconst char *s1, %sconst char *s2)\n" :
          "",
          register_scs, register_scs);
  #if USE_DOWNCASE_TABLE
  printf ("{\n"
          "  for (;;)\n"
          "    {\n"
          "      unsigned char c1 = gperf_downcase[(unsigned char)*s1++];\n"
          "      unsigned char c2 = gperf_downcase[(unsigned char)*s2++];\n"
          "      if (c1 != 0 && c1 == c2)\n"
          "        continue;\n"
          "      return (int)c1 - (int)c2;\n"
          "    }\n"
          "}\n");
  #else
  printf ("{\n"
          "  for (;;)\n"
          "    {\n"
          "      unsigned char c1 = *s1++;\n"
          "      unsigned char c2 = *s2++;\n"
          "      if (c1 >= 'A' && c1 <= 'Z')\n"
          "        c1 += 'a' - 'A';\n"
          "      if (c2 >= 'A' && c2 <= 'Z')\n"
          "        c2 += 'a' - 'A';\n"
          "      if (c1 != 0 && c1 == c2)\n"
          "        continue;\n"
          "      return (int)c1 - (int)c2;\n"
          "    }\n"
          "}\n");
  #endif
  printf ("#endif\n\n");
}

/* Output gperf's ASCII-case insensitive strncmp replacement.  */

static void
output_upperlower_strncmp ()
{
  printf ("#ifndef GPERF_CASE_STRNCMP\n"
          "#define GPERF_CASE_STRNCMP 1\n"
          "static int\n"
          "gperf_case_strncmp ");
  printf (option[KRC] ?
               "(s1, s2, n)\n"
          "     %schar *s1;\n"
          "     %schar *s2;\n"
          "     %ssize_t n;\n" :
          option[C] ?
               "(s1, s2, n)\n"
          "     %sconst char *s1;\n"
          "     %sconst char *s2;\n"
          "     %ssize_t n;\n" :
          option[ANSIC] | option[CPLUSPLUS] ?
               "(%sconst char *s1, %sconst char *s2, %ssize_t n)\n" :
          "",
          register_scs, register_scs, register_scs);
  #if USE_DOWNCASE_TABLE
  printf ("{\n"
          "  for (; n > 0;)\n"
          "    {\n"
          "      unsigned char c1 = gperf_downcase[(unsigned char)*s1++];\n"
          "      unsigned char c2 = gperf_downcase[(unsigned char)*s2++];\n"
          "      if (c1 != 0 && c1 == c2)\n"
          "        {\n"
          "          n--;\n"
          "          continue;\n"
          "        }\n"
          "      return (int)c1 - (int)c2;\n"
          "    }\n"
          "  return 0;\n"
          "}\n");
  #else
  printf ("{\n"
          "  for (; n > 0;)\n"
          "    {\n"
          "      unsigned char c1 = *s1++;\n"
          "      unsigned char c2 = *s2++;\n"
          "      if (c1 >= 'A' && c1 <= 'Z')\n"
          "        c1 += 'a' - 'A';\n"
          "      if (c2 >= 'A' && c2 <= 'Z')\n"
          "        c2 += 'a' - 'A';\n"
          "      if (c1 != 0 && c1 == c2)\n"
          "        {\n"
          "          n--;\n"
          "          continue;\n"
          "        }\n"
          "      return (int)c1 - (int)c2;\n"
          "    }\n"
          "  return 0;\n"
          "}\n");
  #endif
  printf ("#endif\n\n");
}

/* Output gperf's ASCII-case insensitive memcmp replacement.  */

static void
output_upperlower_memcmp ()
{
  printf ("#ifndef GPERF_CASE_MEMCMP\n"
          "#define GPERF_CASE_MEMCMP 1\n"
          "static int\n"
          "gperf_case_memcmp ");
  printf (option[KRC] ?
               "(s1, s2, n)\n"
          "     %schar *s1;\n"
          "     %schar *s2;\n"
          "     %ssize_t n;\n" :
          option[C] ?
               "(s1, s2, n)\n"
          "     %sconst char *s1;\n"
          "     %sconst char *s2;\n"
          "     %ssize_t n;\n" :
          option[ANSIC] | option[CPLUSPLUS] ?
               "(%sconst char *s1, %sconst char *s2, %ssize_t n)\n" :
          "",
          register_scs, register_scs, register_scs);
  #if USE_DOWNCASE_TABLE
  printf ("{\n"
          "  for (; n > 0;)\n"
          "    {\n"
          "      unsigned char c1 = gperf_downcase[(unsigned char)*s1++];\n"
          "      unsigned char c2 = gperf_downcase[(unsigned char)*s2++];\n"
          "      if (c1 == c2)\n"
          "        {\n"
          "          n--;\n"
          "          continue;\n"
          "        }\n"
          "      return (int)c1 - (int)c2;\n"
          "    }\n"
          "  return 0;\n"
          "}\n");
  #else
  printf ("{\n"
          "  for (; n > 0;)\n"
          "    {\n"
          "      unsigned char c1 = *s1++;\n"
          "      unsigned char c2 = *s2++;\n"
          "      if (c1 >= 'A' && c1 <= 'Z')\n"
          "        c1 += 'a' - 'A';\n"
          "      if (c2 >= 'A' && c2 <= 'Z')\n"
          "        c2 += 'a' - 'A';\n"
          "      if (c1 == c2)\n"
          "        {\n"
          "          n--;\n"
          "          continue;\n"
          "        }\n"
          "      return (int)c1 - (int)c2;\n"
          "    }\n"
          "  return 0;\n"
          "}\n");
  #endif
  printf ("#endif\n\n");
}

/* ------------------------------------------------------------------------- */

/* Outputs a keyword, as a string: enclosed in double quotes, escaping
   backslashes, double quote and unprintable characters.  */

static void
output_string (const char *key, int len)
{
  putchar ('"');
  for (; len > 0; len--)
    {
      unsigned char c = static_cast<unsigned char>(*key++);
      if (isprint (c))
        {
          if (c == '"' || c == '\\')
            putchar ('\\');
          putchar (c);
        }
      else
        {
          /* Use octal escapes, not hexadecimal escapes, because some old
             C compilers didn't understand hexadecimal escapes, and because
             hexadecimal escapes are not limited to 2 digits, thus needing
             special care if the following character happens to be a digit.  */
          putchar ('\\');
          putchar ('0' + ((c >> 6) & 7));
          putchar ('0' + ((c >> 3) & 7));
          putchar ('0' + (c & 7));
        }
    }
  putchar ('"');
}

/* ------------------------------------------------------------------------- */

/* Outputs a #line directive, referring to the given line number.  */

static void
output_line_directive (unsigned int lineno)
{
  const char *file_name = option.get_input_file_name ();
  if (file_name != NULL)
    {
      printf ("#line %u ", lineno);
      output_string (file_name, strlen (file_name));
      printf ("\n");
    }
}

/* ------------------------------------------------------------------------- */

/* Outputs a type and a const specifier (i.e. "const " or "").
   The output is terminated with a space.  */

static void
output_const_type (const char *const_string, const char *type_string)
{
  if (type_string[strlen(type_string)-1] == '*')
    /* For pointer types, put the 'const' after the type.  */
    printf ("%s %s", type_string, const_string);
  else
    /* For scalar or struct types, put the 'const' before the type.  */
    printf ("%s%s ", const_string, type_string);
}

/* ----------------------- Output_Expr and subclasses ----------------------- */

/* This class outputs a general expression.  */

struct Output_Expr
{
  virtual void          output_expr () const = 0;
                        Output_Expr () {}
  virtual               ~Output_Expr () {}
};

/* This class outputs an expression formed by a single string.  */

struct Output_Expr1 : public Output_Expr
{
  virtual void          output_expr () const;
                        Output_Expr1 (const char *piece1) : _p1 (piece1) {}
  virtual               ~Output_Expr1 () {}
private:
  const char *_p1;
};

void Output_Expr1::output_expr () const
{
  printf ("%s", _p1);
}

#if 0 /* unused */

/* This class outputs an expression formed by the concatenation of two
   strings.  */

struct Output_Expr2 : public Output_Expr
{
  virtual void          output_expr () const;
                        Output_Expr2 (const char *piece1, const char *piece2)
                          : _p1 (piece1), _p2 (piece2) {}
  virtual               ~Output_Expr2 () {}
private:
  const char *_p1;
  const char *_p2;
};

void Output_Expr2::output_expr () const
{
  printf ("%s%s", _p1, _p2);
}

#endif

/* --------------------- Output_Compare and subclasses --------------------- */

/* This class outputs a comparison expression.  */

struct Output_Compare
{
  /* Outputs the comparison expression.
     expr1 outputs a simple expression of type 'const char *' referring to
     the string being looked up.  expr2 outputs a simple expression of type
     'const char *' referring to the constant string stored in the gperf
     generated hash table.  */
  virtual void          output_comparison (const Output_Expr& expr1,
                                           const Output_Expr& expr2) const = 0;
  /* Outputs the comparison expression for the first byte.
     Returns true if the this comparison is complete.  */
  bool                  output_firstchar_comparison (const Output_Expr& expr1,
                                                     const Output_Expr& expr2) const;
                        Output_Compare () {}
  virtual               ~Output_Compare () {}
};

bool Output_Compare::output_firstchar_comparison (const Output_Expr& expr1,
                                                  const Output_Expr& expr2) const
{
  /* First, we emit a comparison of the first byte of the two strings.
     This catches most cases where the string being looked up is not in the
     hash table but happens to have the same hash code as an element of the
     hash table.  */
  if (option[UPPERLOWER])
    {
      /* Incomplete comparison, just for speedup.  */
      printf ("(((unsigned char)*");
      expr1.output_expr ();
      printf (" ^ (unsigned char)*");
      expr2.output_expr ();
      printf (") & ~32) == 0");
      return false;
    }
  else
    {
      /* Complete comparison.  */
      printf ("*");
      expr1.output_expr ();
      printf (" == *");
      expr2.output_expr ();
      return true;
    }
}

/* This class outputs a comparison using strcmp.  */

struct Output_Compare_Strcmp : public Output_Compare
{
  virtual void          output_comparison (const Output_Expr& expr1,
                                           const Output_Expr& expr2) const;
                        Output_Compare_Strcmp () {}
  virtual               ~Output_Compare_Strcmp () {}
};

void Output_Compare_Strcmp::output_comparison (const Output_Expr& expr1,
                                               const Output_Expr& expr2) const
{
  bool firstchar_done = output_firstchar_comparison (expr1, expr2);
  printf (" && !");
  if (option[UPPERLOWER])
    printf ("gperf_case_");
  printf ("strcmp (");
  if (firstchar_done)
    {
      expr1.output_expr ();
      printf (" + 1, ");
      expr2.output_expr ();
      printf (" + 1");
    }
  else
    {
      expr1.output_expr ();
      printf (", ");
      expr2.output_expr ();
    }
  printf (")");
}

/* This class outputs a comparison using strncmp.
   Note that the length of expr1 will be available through the local variable
   'len'.  */

struct Output_Compare_Strncmp : public Output_Compare
{
  virtual void          output_comparison (const Output_Expr& expr1,
                                           const Output_Expr& expr2) const;
                        Output_Compare_Strncmp () {}
  virtual               ~Output_Compare_Strncmp () {}
};

void Output_Compare_Strncmp::output_comparison (const Output_Expr& expr1,
                                                const Output_Expr& expr2) const
{
  bool firstchar_done = output_firstchar_comparison (expr1, expr2);
  printf (" && !");
  if (option[UPPERLOWER])
    printf ("gperf_case_");
  printf ("strncmp (");
  if (firstchar_done)
    {
      expr1.output_expr ();
      printf (" + 1, ");
      expr2.output_expr ();
      printf (" + 1, len - 1");
    }
  else
    {
      expr1.output_expr ();
      printf (", ");
      expr2.output_expr ();
      printf (", len");
    }
  printf (") && ");
  expr2.output_expr ();
  printf ("[len] == '\\0'");
}

/* This class outputs a comparison using memcmp.
   Note that the length of expr1 (available through the local variable 'len')
   must be verified to be equal to the length of expr2 prior to this
   comparison.  */

struct Output_Compare_Memcmp : public Output_Compare
{
  virtual void          output_comparison (const Output_Expr& expr1,
                                           const Output_Expr& expr2) const;
                        Output_Compare_Memcmp () {}
  virtual               ~Output_Compare_Memcmp () {}
};

void Output_Compare_Memcmp::output_comparison (const Output_Expr& expr1,
                                               const Output_Expr& expr2) const
{
  bool firstchar_done = output_firstchar_comparison (expr1, expr2);
  printf (" && !");
  if (option[UPPERLOWER])
    printf ("gperf_case_");
  printf ("memcmp (");
  if (firstchar_done)
    {
      expr1.output_expr ();
      printf (" + 1, ");
      expr2.output_expr ();
      printf (" + 1, len - 1");
    }
  else
    {
      expr1.output_expr ();
      printf (", ");
      expr2.output_expr ();
      printf (", len");
    }
  printf (")");
}

/* ------------------------------------------------------------------------- */

/* Generates a C expression for an asso_values[] index.  */

void
Output::output_asso_values_index (int pos) const
{
  if (pos == Positions::LASTCHAR)
    printf ("str[len - 1]");
  else
    {
      printf ("str[%d]", pos);
      if (_alpha_inc[pos])
        printf ("+%u", _alpha_inc[pos]);
    }
}

/* Generates a C expression for an asso_values[] reference.  */

void
Output::output_asso_values_ref (int pos) const
{
  printf ("asso_values[");
  /* Always cast to unsigned char.  This is necessary when the alpha_inc
     is nonzero, and also avoids a gcc warning "subscript has type 'char'".  */
  if (option[CPLUSPLUS])
    {
      /* In C++, a C style cast may lead to a 'warning: use of old-style cast'.
         Therefore prefer the C++ style cast syntax.  */
      printf ("static_cast<unsigned char>(");
      output_asso_values_index (pos);
      printf (")");
    }
  else
    {
      printf ("(unsigned char)");
      output_asso_values_index (pos);
    }
  printf ("]");
}

/* Generates C code for the hash function that returns the
   proper encoding for each keyword.
   The hash function has the signature
     unsigned int <hash> (const char *str, size_t len).  */

void
Output::output_hash_function () const
{
  /* Output the function's head.  */
  if (option[CPLUSPLUS])
    printf ("inline ");
  else if (option[KRC] | option[C] | option[ANSIC])
    printf ("#ifdef __GNUC__\n"
            "__inline\n"
            "#else\n"
            "#ifdef __cplusplus\n"
            "inline\n"
            "#endif\n"
            "#endif\n");

  /* Does the function use the 'str' argument?  */
  bool uses_str = (_key_positions.get_size() > 0);
  /* Does the function use the 'len' argument?  */
  bool uses_len =
     (_hash_includes_len
      || (_key_positions.get_size() > 0
          && (_key_positions[0] >= _min_key_len
              || _key_positions[_key_positions.get_size() - 1] == Positions::LASTCHAR)));
  if (!uses_str || !uses_len)
    /* Pacify lint.  */
    printf ("/*ARGSUSED*/\n");

  if (option[KRC] | option[C] | option[ANSIC])
    printf ("static ");
  printf ("unsigned int\n");
  if (option[CPLUSPLUS])
    printf ("%s::", option.get_class_name ());
  printf ("%s ", option.get_hash_name ());
  /* We better not use [[__maybe_unused__]] or __attribute__ ((__unused__))
     because support for these syntaxes in the compilers is constantly
     changing.  */
  printf (option[KRC] ?
                 "(str, len)\n"
            "     %schar *str;\n"
            "     %ssize_t len;\n" :
          option[C] ?
                 "(str, len)\n"
            "     %sconst char *str;\n"
            "     %ssize_t len;\n" :
          option[ANSIC] | option[CPLUSPLUS] ?
                 "(%sconst char *str, %ssize_t len)\n" :
          "",
          register_scs, register_scs);

  /* Note that when the hash function is called, it has already been verified
     that  min_key_len <= len <= max_key_len.  */

  /* Output the function's body.  */
  printf ("{\n");

  /* First the asso_values array.  */
  if (_key_positions.get_size() > 0)
    {
      /* The values in the asso_values array are all unsigned integers
         <= MAX_HASH_VALUE + 1.  */
      printf ("  static %s%s asso_values[] =\n"
              "    {",
              const_readonly_array,
              smallest_integral_type (_max_hash_value + 1));

      const int columns = 10;

      /* Calculate maximum number of digits required for MAX_HASH_VALUE + 1.  */
      int field_width = 2;
      for (int trunc = _max_hash_value + 1; (trunc /= 10) > 0;)
        field_width++;

      for (unsigned int count = 0; count < _alpha_size; count++)
        {
          if (count > 0)
            printf (",");
          if ((count % columns) == 0)
            printf ("\n     ");
          printf ("%*d", field_width, _asso_values[count]);
        }

      printf ("\n"
              "    };\n");
    }

  if (!uses_str)
    /* The function does not use the 'str' argument.
       Silence "gcc -Wunused-parameter".  */
    printf ("  (void) str;\n");
  if (!uses_len)
    /* The function does not use the 'len' argument.
       Silence "gcc -Wunused-parameter".  */
    printf ("  (void) len;\n");

  if (_key_positions.get_size() == 0)
    {
      /* Trivial case: No key positions at all.  */
      printf ("  return %s;\n",
              _hash_includes_len ? "len" : "0");
    }
  else
    {
      /* Iterate through the key positions.  Remember that Positions::sort()
         has sorted them in decreasing order, with Positions::LASTCHAR coming
         last.  */
      PositionIterator iter = _key_positions.iterator(_max_key_len);
      int key_pos;

      /* Get the highest key position.  */
      key_pos = iter.next ();

      if (key_pos == Positions::LASTCHAR || key_pos < _min_key_len)
        {
          /* We can perform additional optimizations here:
             Write it out as a single expression. Note that the values
             are added as 'int's even though the asso_values array may
             contain 'unsigned char's or 'unsigned short's.  */

          printf ("  return %s",
                  _hash_includes_len ? "len + " : "");

          if (_key_positions.get_size() == 2
              && _key_positions[0] == 0
              && _key_positions[1] == Positions::LASTCHAR)
            /* Optimize special case of "-k 1,$".  */
            {
              output_asso_values_ref (Positions::LASTCHAR);
              printf (" + ");
              output_asso_values_ref (0);
            }
          else
            {
              for (; key_pos != Positions::LASTCHAR; )
                {
                  output_asso_values_ref (key_pos);
                  if ((key_pos = iter.next ()) != PositionIterator::EOS)
                    printf (" + ");
                  else
                    break;
                }

              if (key_pos == Positions::LASTCHAR)
                output_asso_values_ref (Positions::LASTCHAR);
            }

          printf (";\n");
        }
      else
        {
          /* We've got to use the correct, but brute force, technique.  */
          /* Pseudo-statement or comment that avoids a compiler warning or
             lint warning.  */
          const char * const fallthrough_marker =
            "#if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9)))\n"
            "      [[fallthrough]];\n"
            "#elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10)\n"
            "      __attribute__ ((__fallthrough__));\n"
            "#endif\n"
            "      /*FALLTHROUGH*/\n";
          /* It doesn't really matter whether hval is an 'int' or
             'unsigned int', but 'unsigned int' gives fewer warnings.  */
          printf ("  %sunsigned int hval = %s;\n\n"
                  "  switch (%s)\n"
                  "    {\n"
                  "      default:\n",
                  register_scs, _hash_includes_len ? "len" : "0",
                  _hash_includes_len ? "hval" : "len");

          while (key_pos != Positions::LASTCHAR && key_pos >= _max_key_len)
            if ((key_pos = iter.next ()) == PositionIterator::EOS)
              break;

          if (key_pos != PositionIterator::EOS && key_pos != Positions::LASTCHAR)
            {
              int i = key_pos;
              do
                {
                  if (i > key_pos)
                    printf ("%s", fallthrough_marker);
                  for ( ; i > key_pos; i--)
                    printf ("      case %d:\n", i);

                  printf ("        hval += ");
                  output_asso_values_ref (key_pos);
                  printf (";\n");

                  key_pos = iter.next ();
                }
              while (key_pos != PositionIterator::EOS && key_pos != Positions::LASTCHAR);

              if (i >= _min_key_len)
                printf ("%s", fallthrough_marker);
              for ( ; i >= _min_key_len; i--)
                printf ("      case %d:\n", i);
            }

          printf ("        break;\n"
                  "    }\n"
                  "  return hval");
          if (key_pos == Positions::LASTCHAR)
            {
              printf (" + ");
              output_asso_values_ref (Positions::LASTCHAR);
            }
          printf (";\n");
        }
    }
  printf ("}\n\n");
}

/* ------------------------------------------------------------------------- */

/* Prints out a table of keyword lengths, for use with the
   comparison code in generated function 'in_word_set'.
   Only called if option[LENTABLE].  */

void
Output::output_keylength_table () const
{
  const int columns = 14;
  const char * const indent = option[GLOBAL] ? "" : "  ";

  printf ("%sstatic %s%s %s[] =\n"
          "%s  {",
          indent, const_readonly_array,
          smallest_integral_type (_max_key_len),
          option.get_lengthtable_name (),
          indent);

  /* Generate an array of lengths, similar to output_keyword_table.  */
  int index;
  int column;
  KeywordExt_List *temp;

  column = 0;
  for (temp = _head, index = 0; temp; temp = temp->rest())
    {
      KeywordExt *keyword = temp->first();

      /* If generating a switch statement, and there is no user defined type,
         we generate non-duplicates directly in the code.  Only duplicates go
         into the table.  */
      if (option[SWITCH] && !option[TYPE] && !keyword->_duplicate_link)
        continue;

      if (index < keyword->_hash_value && !option[SWITCH] && !option[DUP])
        {
          /* Some blank entries.  */
          for ( ; index < keyword->_hash_value; index++)
            {
              if (index > 0)
                printf (",");
              if ((column++ % columns) == 0)
                printf ("\n%s   ", indent);
              printf ("%3d", 0);
            }
        }

      if (index > 0)
        printf (",");
      if ((column++ % columns) == 0)
        printf("\n%s   ", indent);
      printf ("%3d", keyword->_allchars_length);
      index++;

      /* Deal with duplicates specially.  */
      if (keyword->_duplicate_link) // implies option[DUP]
        for (KeywordExt *links = keyword->_duplicate_link; links; links = links->_duplicate_link)
          {
            printf (",");
            if ((column++ % columns) == 0)
              printf("\n%s   ", indent);
            printf ("%3d", links->_allchars_length);
            index++;
          }
    }

  printf ("\n%s  };\n", indent);
  if (option[GLOBAL])
    printf ("\n");
}

/* ------------------------------------------------------------------------- */

/* Prints out the string pool, containing the strings of the keyword table.
   Only called if option[SHAREDLIB].  */

void
Output::output_string_pool () const
{
  const char * const indent = option[TYPE] || option[GLOBAL] ? "" : "  ";
  int index;
  KeywordExt_List *temp;

  printf ("%sstruct %s_t\n"
          "%s  {\n",
          indent, option.get_stringpool_name (), indent);
  for (temp = _head, index = 0; temp; temp = temp->rest())
    {
      KeywordExt *keyword = temp->first();

      /* If generating a switch statement, and there is no user defined type,
         we generate non-duplicates directly in the code.  Only duplicates go
         into the table.  */
      if (option[SWITCH] && !option[TYPE] && !keyword->_duplicate_link)
        continue;

      if (!option[SWITCH] && !option[DUP])
        index = keyword->_hash_value;

      printf ("%s    char %s_str%d[sizeof(",
              indent, option.get_stringpool_name (), index);
      output_string (keyword->_allchars, keyword->_allchars_length);
      printf (")];\n");

      /* Deal with duplicates specially.  */
      if (keyword->_duplicate_link) // implies option[DUP]
        for (KeywordExt *links = keyword->_duplicate_link; links; links = links->_duplicate_link)
          if (!(links->_allchars_length == keyword->_allchars_length
                && memcmp (links->_allchars, keyword->_allchars,
                           keyword->_allchars_length) == 0))
            {
              index++;
              printf ("%s    char %s_str%d[sizeof(",
                      indent, option.get_stringpool_name (), index);
              output_string (links->_allchars, links->_allchars_length);
              printf (")];\n");
            }

      index++;
    }
  printf ("%s  };\n",
          indent);

  printf ("%sstatic %sstruct %s_t %s_contents =\n"
          "%s  {\n",
          indent, const_readonly_array, option.get_stringpool_name (),
          option.get_stringpool_name (), indent);
  for (temp = _head, index = 0; temp; temp = temp->rest())
    {
      KeywordExt *keyword = temp->first();

      /* If generating a switch statement, and there is no user defined type,
         we generate non-duplicates directly in the code.  Only duplicates go
         into the table.  */
      if (option[SWITCH] && !option[TYPE] && !keyword->_duplicate_link)
        continue;

      if (index > 0)
        printf (",\n");

      if (!option[SWITCH] && !option[DUP])
        index = keyword->_hash_value;

      printf ("%s    ",
              indent);
      output_string (keyword->_allchars, keyword->_allchars_length);

      /* Deal with duplicates specially.  */
      if (keyword->_duplicate_link) // implies option[DUP]
        for (KeywordExt *links = keyword->_duplicate_link; links; links = links->_duplicate_link)
          if (!(links->_allchars_length == keyword->_allchars_length
                && memcmp (links->_allchars, keyword->_allchars,
                           keyword->_allchars_length) == 0))
            {
              index++;
              printf (",\n");
              printf ("%s    ",
                      indent);
              output_string (links->_allchars, links->_allchars_length);
            }

      index++;
    }
  if (index > 0)
    printf ("\n");
  printf ("%s  };\n",
          indent);
  printf ("%s#define %s ((%schar *) &%s_contents)\n",
          indent, option.get_stringpool_name (), const_always,
          option.get_stringpool_name ());
  if (option[GLOBAL])
    printf ("\n");
}

/* ------------------------------------------------------------------------- */

static void
output_keyword_entry (KeywordExt *temp, int stringpool_index, const char *indent, bool is_duplicate)
{
  if (option[TYPE])
    output_line_directive (temp->_lineno);
  printf ("%s    ", indent);
  if (option[TYPE])
    printf ("{");
  if (option[SHAREDLIB])
    /* How to determine a certain offset in stringpool at compile time?
       - The standard way would be to use the 'offsetof' macro.  But it is only
         defined in <stddef.h>, and <stddef.h> is not among the prerequisite
         header files that the user must #include.
       - The next best way would be to take the address and cast to 'intptr_t'
         or 'uintptr_t'.  But these types are only defined in <stdint.h>, and
         <stdint.h> is not among the prerequisite header files that the user
         must #include.
       - The next best approximation of 'uintptr_t' is 'size_t'.  It is defined
         in the prerequisite header <string.h>.
       - The types 'long' and 'unsigned long' do work as well, but on 64-bit
         native Windows platforms, they don't have the same size as pointers
         and therefore generate warnings.  */
    printf ("(int)(size_t)&((struct %s_t *)0)->%s_str%d",
            option.get_stringpool_name (), option.get_stringpool_name (),
            stringpool_index);
  else
    output_string (temp->_allchars, temp->_allchars_length);
  if (option[TYPE])
    {
      if (strlen (temp->_rest) > 0)
        printf (",%s", temp->_rest);
      printf ("}");
    }
  if (option[DEBUG])
    {
      printf (" /* ");
      if (is_duplicate)
        printf ("hash value duplicate, ");
      else
        printf ("hash value = %d, ", temp->_hash_value);
      printf ("index = %d */", temp->_final_index);
    }
}

static void
output_keyword_blank_entries (int count, const char *indent)
{
  int columns;
  if (option[TYPE])
    {
      columns = 58 / (4 + (option[SHAREDLIB] ? 2 : option[NULLSTRINGS] ? 8 : 2)
                        + strlen (option.get_initializer_suffix()));
      if (columns == 0)
        columns = 1;
    }
  else
    {
      columns = (option[SHAREDLIB] ? 9 : option[NULLSTRINGS] ? 4 : 9);
    }
  int column = 0;
  for (int i = 0; i < count; i++)
    {
      if ((column % columns) == 0)
        {
          if (i > 0)
            printf (",\n");
          printf ("%s    ", indent);
        }
      else
        {
          if (i > 0)
            printf (", ");
        }
      if (option[TYPE])
        printf ("{");
      if (option[SHAREDLIB])
        printf ("-1");
      else
        {
          if (option[NULLSTRINGS])
            printf ("(char*)0");
          else
            printf ("\"\"");
        }
      if (option[TYPE])
        printf ("%s}", option.get_initializer_suffix());
      column++;
    }
}

/* Prints out the array containing the keywords for the hash function.  */

void
Output::output_keyword_table () const
{
  const char *indent = option[GLOBAL] ? "" : "  ";
  int index;
  KeywordExt_List *temp;

  /* Avoid compiler warnings "warning: missing initializer for field ..."
     for each of the blank entries.  */
  bool silence_missing_initializer_warning =
    option[TYPE] && !option.has_initializer_suffix();
  const char *preprocessor_condition =
    "(defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || (defined __clang__ && __clang_major__ >= 3)";
  if (silence_missing_initializer_warning)
    {
      printf ("#if %s\n", preprocessor_condition);
      printf ("#pragma GCC diagnostic push\n");
      printf ("#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n");
      printf ("#endif\n");
    }

  printf ("%sstatic ",
          indent);
  output_const_type (const_readonly_array, _wordlist_eltype);
  printf ("%s[] =\n"
          "%s  {\n",
          option.get_wordlist_name (),
          indent);

  /* Generate an array of reserved words at appropriate locations.  */

  for (temp = _head, index = 0; temp; temp = temp->rest())
    {
      KeywordExt *keyword = temp->first();

      /* If generating a switch statement, and there is no user defined type,
         we generate non-duplicates directly in the code.  Only duplicates go
         into the table.  */
      if (option[SWITCH] && !option[TYPE] && !keyword->_duplicate_link)
        continue;

      if (index > 0)
        printf (",\n");

      if (index < keyword->_hash_value && !option[SWITCH] && !option[DUP])
        {
          /* Some blank entries.  */
          output_keyword_blank_entries (keyword->_hash_value - index, indent);
          printf (",\n");
          index = keyword->_hash_value;
        }

      keyword->_final_index = index;

      output_keyword_entry (keyword, index, indent, false);

      /* Deal with duplicates specially.  */
      if (keyword->_duplicate_link) // implies option[DUP]
        for (KeywordExt *links = keyword->_duplicate_link; links; links = links->_duplicate_link)
          {
            links->_final_index = ++index;
            printf (",\n");
            int stringpool_index =
              (links->_allchars_length == keyword->_allchars_length
               && memcmp (links->_allchars, keyword->_allchars,
                          keyword->_allchars_length) == 0
               ? keyword->_final_index
               : links->_final_index);
            output_keyword_entry (links, stringpool_index, indent, true);
          }

      index++;
    }
  if (index > 0)
    printf ("\n");

  printf ("%s  };\n", indent);

  if (silence_missing_initializer_warning)
    {
      printf ("#if %s\n", preprocessor_condition);
      printf ("#pragma GCC diagnostic pop\n");
      printf ("#endif\n");
    }
  printf ("\n");
}

/* ------------------------------------------------------------------------- */

/* Generates the large, sparse table that maps hash values into
   the smaller, contiguous range of the keyword table.  */

void
Output::output_lookup_array () const
{
  if (option[DUP])
    {
      const int DEFAULT_VALUE = -1;

      /* Because of the way output_keyword_table works, every duplicate set is
         stored contiguously in the wordlist array.  */
      struct duplicate_entry
        {
          int hash_value; /* Hash value for this particular duplicate set.  */
          int index;      /* Index into the main keyword storage array.  */
          int count;      /* Number of consecutive duplicates at this index.  */
        };

      duplicate_entry *duplicates = new duplicate_entry[_total_duplicates];
      int *lookup_array = new int[_max_hash_value + 1 + 2*_total_duplicates];
      int lookup_array_size = _max_hash_value + 1;
      duplicate_entry *dup_ptr = &duplicates[0];
      int *lookup_ptr = &lookup_array[_max_hash_value + 1 + 2*_total_duplicates];

      while (lookup_ptr > lookup_array)
        *--lookup_ptr = DEFAULT_VALUE;

      /* Now dup_ptr = &duplicates[0] and lookup_ptr = &lookup_array[0].  */

      for (KeywordExt_List *temp = _head; temp; temp = temp->rest())
        {
          int hash_value = temp->first()->_hash_value;
          lookup_array[hash_value] = temp->first()->_final_index;
          if (option[DEBUG])
            fprintf (stderr, "keyword = %.*s, index = %d\n",
                     temp->first()->_allchars_length, temp->first()->_allchars, temp->first()->_final_index);
          if (temp->first()->_duplicate_link)
            {
              /* Start a duplicate entry.  */
              dup_ptr->hash_value = hash_value;
              dup_ptr->index = temp->first()->_final_index;
              dup_ptr->count = 1;

              for (KeywordExt *ptr = temp->first()->_duplicate_link; ptr; ptr = ptr->_duplicate_link)
                {
                  dup_ptr->count++;
                  if (option[DEBUG])
                    fprintf (stderr,
                             "static linked keyword = %.*s, index = %d\n",
                             ptr->_allchars_length, ptr->_allchars, ptr->_final_index);
                }
              assert (dup_ptr->count >= 2);
              dup_ptr++;
            }
        }

      while (dup_ptr > duplicates)
        {
          dup_ptr--;

          if (option[DEBUG])
            fprintf (stderr,
                     "dup_ptr[%lu]: hash_value = %d, index = %d, count = %d\n",
                     static_cast<unsigned long>(dup_ptr - duplicates),
                     dup_ptr->hash_value, dup_ptr->index, dup_ptr->count);

          int i;
          /* Start searching for available space towards the right part
             of the lookup array.  */
          for (i = dup_ptr->hash_value; i < lookup_array_size-1; i++)
            if (lookup_array[i] == DEFAULT_VALUE
                && lookup_array[i + 1] == DEFAULT_VALUE)
              goto found_i;
          /* If we didn't find it to the right look to the left instead...  */
          for (i = dup_ptr->hash_value-1; i >= 0; i--)
            if (lookup_array[i] == DEFAULT_VALUE
                && lookup_array[i + 1] == DEFAULT_VALUE)
              goto found_i;
          /* Append to the end of lookup_array.  */
          i = lookup_array_size;
          lookup_array_size += 2;
        found_i:
          /* Put in an indirection from dup_ptr->_hash_value to i.
             At i and i+1 store dup_ptr->_final_index and dup_ptr->count.  */
          assert (lookup_array[dup_ptr->hash_value] == dup_ptr->index);
          lookup_array[dup_ptr->hash_value] = - 1 - _total_keys - i;
          lookup_array[i] = - _total_keys + dup_ptr->index;
          lookup_array[i + 1] = - dup_ptr->count;
          /* All these three values are <= -2, distinct from DEFAULT_VALUE.  */
        }

      /* The values of the lookup array are now known.  */

      int min = INT_MAX;
      int max = INT_MIN;
      lookup_ptr = lookup_array + lookup_array_size;
      while (lookup_ptr > lookup_array)
        {
          int val = *--lookup_ptr;
          if (min > val)
            min = val;
          if (max < val)
            max = val;
        }

      const char *indent = option[GLOBAL] ? "" : "  ";
      printf ("%sstatic %s%s lookup[] =\n"
              "%s  {",
              indent, const_readonly_array, smallest_integral_type (min, max),
              indent);

      int field_width;
      /* Calculate maximum number of digits required for MIN..MAX.  */
      {
        field_width = 2;
        for (int trunc = max; (trunc /= 10) > 0;)
          field_width++;
      }
      if (min < 0)
        {
          int neg_field_width = 2;
          for (int trunc = -min; (trunc /= 10) > 0;)
            neg_field_width++;
          neg_field_width++; /* account for the minus sign */
          if (field_width < neg_field_width)
            field_width = neg_field_width;
        }

      const int columns = 42 / field_width;
      int column;

      column = 0;
      for (int i = 0; i < lookup_array_size; i++)
        {
          if (i > 0)
            printf (",");
          if ((column++ % columns) == 0)
            printf("\n%s   ", indent);
          printf ("%*d", field_width, lookup_array[i]);
        }
      printf ("\n%s  };\n\n", indent);

      delete[] duplicates;
      delete[] lookup_array;
    }
}

/* ------------------------------------------------------------------------- */

/* Generate all pools needed for the lookup function.  */

void
Output::output_lookup_pools () const
{
  if (option[SWITCH])
    {
      if (option[TYPE] || (option[DUP] && _total_duplicates > 0))
        output_string_pool ();
    }
  else
    {
      output_string_pool ();
    }
}

/* Generate all the tables needed for the lookup function.  */

void
Output::output_lookup_tables () const
{
  if (option[SWITCH])
    {
      /* Use the switch in place of lookup table.  */
      if (option[LENTABLE] && (option[DUP] && _total_duplicates > 0))
        output_keylength_table ();
      if (option[TYPE] || (option[DUP] && _total_duplicates > 0))
        output_keyword_table ();
    }
  else
    {
      /* Use the lookup table, in place of switch.  */
      if (option[LENTABLE])
        output_keylength_table ();
      output_keyword_table ();
      output_lookup_array ();
    }
}

/* ------------------------------------------------------------------------- */

/* Output a single switch case (including duplicates).  Advance list.  */

static KeywordExt_List *
output_switch_case (KeywordExt_List *list, int indent, int *jumps_away)
{
  if (option[DEBUG])
    printf ("%*s/* hash value = %4d, keyword = \"%.*s\" */\n",
            indent, "", list->first()->_hash_value, list->first()->_allchars_length, list->first()->_allchars);

  if (option[DUP] && list->first()->_duplicate_link)
    {
      if (option[LENTABLE])
        printf ("%*slengthptr = &%s[%d];\n",
                indent, "", option.get_lengthtable_name (), list->first()->_final_index);
      printf ("%*swordptr = &%s[%d];\n",
              indent, "", option.get_wordlist_name (), list->first()->_final_index);

      int count = 0;
      for (KeywordExt *links = list->first(); links; links = links->_duplicate_link)
        count++;

      printf ("%*swordendptr = wordptr + %d;\n"
              "%*sgoto multicompare;\n",
              indent, "", count,
              indent, "");
      *jumps_away = 1;
    }
  else
    {
      if (option[LENTABLE])
        {
          printf ("%*sif (len == %d)\n"
                  "%*s  {\n",
                  indent, "", list->first()->_allchars_length,
                  indent, "");
          indent += 4;
        }
      printf ("%*sresword = ",
              indent, "");
      if (option[TYPE])
        printf ("&%s[%d]", option.get_wordlist_name (), list->first()->_final_index);
      else
        output_string (list->first()->_allchars, list->first()->_allchars_length);
      printf (";\n");
      printf ("%*sgoto compare;\n",
              indent, "");
      if (option[LENTABLE])
        {
          indent -= 4;
          printf ("%*s  }\n",
                  indent, "");
        }
      else
        *jumps_away = 1;
    }

  return list->rest();
}

/* Output a total of size cases, grouped into num_switches switch statements,
   where 0 < num_switches <= size.  */

static void
output_switches (KeywordExt_List *list, int num_switches, int size, int min_hash_value, int max_hash_value, int indent)
{
  if (option[DEBUG])
    printf ("%*s/* know %d <= key <= %d, contains %d cases */\n",
            indent, "", min_hash_value, max_hash_value, size);

  if (num_switches > 1)
    {
      int part1 = num_switches / 2;
      int part2 = num_switches - part1;
      int size1 = static_cast<int>(static_cast<double>(size) / static_cast<double>(num_switches) * static_cast<double>(part1) + 0.5);
      int size2 = size - size1;

      KeywordExt_List *temp = list;
      for (int count = size1; count > 0; count--)
        temp = temp->rest();

      printf ("%*sif (key < %d)\n"
              "%*s  {\n",
              indent, "", temp->first()->_hash_value,
              indent, "");

      output_switches (list, part1, size1, min_hash_value, temp->first()->_hash_value-1, indent+4);

      printf ("%*s  }\n"
              "%*selse\n"
              "%*s  {\n",
              indent, "", indent, "", indent, "");

      output_switches (temp, part2, size2, temp->first()->_hash_value, max_hash_value, indent+4);

      printf ("%*s  }\n",
              indent, "");
    }
  else
    {
      /* Output a single switch.  */
      int lowest_case_value = list->first()->_hash_value;
      if (size == 1)
        {
          int jumps_away = 0;
          assert (min_hash_value <= lowest_case_value);
          assert (lowest_case_value <= max_hash_value);
          if (min_hash_value == max_hash_value)
            output_switch_case (list, indent, &jumps_away);
          else
            {
              printf ("%*sif (key == %d)\n"
                      "%*s  {\n",
                      indent, "", lowest_case_value,
                      indent, "");
              output_switch_case (list, indent+4, &jumps_away);
              printf ("%*s  }\n",
                      indent, "");
            }
        }
      else
        {
          if (lowest_case_value == 0)
            printf ("%*sswitch (key)\n", indent, "");
          else
            printf ("%*sswitch (key - %d)\n", indent, "", lowest_case_value);
          printf ("%*s  {\n",
                  indent, "");
          for (; size > 0; size--)
            {
              int jumps_away = 0;
              printf ("%*s    case %d:\n",
                      indent, "", list->first()->_hash_value - lowest_case_value);
              list = output_switch_case (list, indent+6, &jumps_away);
              if (!jumps_away)
                printf ("%*s      break;\n",
                        indent, "");
            }
          printf ("%*s  }\n",
                  indent, "");
        }
    }
}

/* Generates C code to perform the keyword lookup.  */

void
Output::output_lookup_function_body (const Output_Compare& comparison) const
{
  /* An expression equivalent to NULL, of type _return_type.
     We don't use plain "0", because that would trigger a gcc 15 warning
     if the warning option -Wzero-as-null-pointer-constant is in use.
     In C++, using "nullptr" if __cplusplus >= 201103L would be possible,
     but is not worth the trouble.  */
  char *null_expression = new char[17 + strlen (_return_type) + 1];
  if (option[CPLUSPLUS])
    sprintf (null_expression, "static_cast<%s> (0)", _return_type);
  else
    sprintf (null_expression, "(%s) 0", _return_type);

  printf ("  if (len <= %sMAX_WORD_LENGTH && len >= %sMIN_WORD_LENGTH)\n"
          "    {\n"
          "      %sunsigned int key = %s (str, len);\n\n",
          option.get_constants_prefix (), option.get_constants_prefix (),
          register_scs, option.get_hash_name ());

  if (option[SWITCH])
    {
      int switch_size = num_hash_values ();
      int num_switches = option.get_total_switches ();
      if (num_switches > switch_size)
        num_switches = switch_size;

      printf ("      if (key <= %sMAX_HASH_VALUE",
              option.get_constants_prefix ());
      if (_min_hash_value > 0)
        printf (" && key >= %sMIN_HASH_VALUE",
                option.get_constants_prefix ());
      printf (")\n"
              "        {\n");
      if (option[DUP] && _total_duplicates > 0)
        {
          if (option[LENTABLE])
            printf ("          %s%s%s *lengthptr;\n",
                    register_scs, const_always,
                    smallest_integral_type (_max_key_len));
          printf ("          %s",
                  register_scs);
          output_const_type (const_readonly_array, _wordlist_eltype);
          printf ("*wordptr;\n");
          printf ("          %s",
                  register_scs);
          output_const_type (const_readonly_array, _wordlist_eltype);
          printf ("*wordendptr;\n");
        }
      if (option[TYPE])
        {
          printf ("          %s",
                  register_scs);
          output_const_type (const_readonly_array, _struct_tag);
          printf ("*resword;\n\n");
        }
      else
        printf ("          %s%sresword;\n\n",
                register_scs, _struct_tag);

      output_switches (_head, num_switches, switch_size, _min_hash_value, _max_hash_value, 10);

      printf ("          return %s;\n",
              null_expression);
      if (option[DUP] && _total_duplicates > 0)
        {
          int indent = 8;
          printf ("%*smulticompare:\n"
                  "%*s  while (wordptr < wordendptr)\n"
                  "%*s    {\n",
                  indent, "", indent, "", indent, "");
          if (option[LENTABLE])
            {
              printf ("%*s      if (len == *lengthptr)\n"
                      "%*s        {\n",
                      indent, "", indent, "");
              indent += 4;
            }
          printf ("%*s      %s%schar *s = ",
                  indent, "", register_scs, const_always);
          if (option[TYPE])
            printf ("wordptr->%s", option.get_slot_name ());
          else
            printf ("*wordptr");
          if (option[SHAREDLIB])
            printf (" + %s",
                    option.get_stringpool_name ());
          printf (";\n\n"
                  "%*s      if (",
                  indent, "");
          comparison.output_comparison (Output_Expr1 ("str"), Output_Expr1 ("s"));
          printf (")\n"
                  "%*s        return %s;\n",
                  indent, "",
                  option[TYPE] ? "wordptr" : "s");
          if (option[LENTABLE])
            {
              indent -= 4;
              printf ("%*s        }\n",
                      indent, "");
            }
          if (option[LENTABLE])
            printf ("%*s      lengthptr++;\n",
                    indent, "");
          printf ("%*s      wordptr++;\n"
                  "%*s    }\n"
                  "%*s  return %s;\n",
                  indent, "", indent, "", indent, "", null_expression);
        }
      printf ("        compare:\n");
      if (option[TYPE])
        {
          printf ("          {\n"
                  "            %s%schar *s = resword->%s",
                  register_scs, const_always, option.get_slot_name ());
          if (option[SHAREDLIB])
            printf (" + %s",
                    option.get_stringpool_name ());
          printf (";\n\n"
                  "            if (");
          comparison.output_comparison (Output_Expr1 ("str"), Output_Expr1 ("s"));
          printf (")\n"
                  "              return resword;\n"
                  "          }\n");
        }
      else
        {
          printf ("          if (");
          comparison.output_comparison (Output_Expr1 ("str"), Output_Expr1 ("resword"));
          printf (")\n"
                  "            return resword;\n");
        }
      printf ("        }\n");
    }
  else
    {
      printf ("      if (key <= %sMAX_HASH_VALUE)\n",
              option.get_constants_prefix ());

      if (option[DUP])
        {
          int indent = 8;
          printf ("%*s{\n"
                  "%*s  %sint index = lookup[key];\n\n"
                  "%*s  if (index >= 0)\n",
                  indent, "", indent, "", register_scs, indent, "");
          if (option[LENTABLE])
            {
              printf ("%*s    {\n"
                      "%*s      if (len == %s[index])\n",
                      indent, "", indent, "", option.get_lengthtable_name ());
              indent += 4;
            }
          printf ("%*s    {\n"
                  "%*s      %s%schar *s = %s[index]",
                  indent, "",
                  indent, "", register_scs, const_always,
                  option.get_wordlist_name ());
          if (option[TYPE])
            printf (".%s", option.get_slot_name ());
          if (option[SHAREDLIB])
            printf (" + %s",
                    option.get_stringpool_name ());
          printf (";\n\n"
                  "%*s      if (",
                  indent, "");
          comparison.output_comparison (Output_Expr1 ("str"), Output_Expr1 ("s"));
          printf (")\n"
                  "%*s        return ",
                  indent, "");
          if (option[TYPE])
            printf ("&%s[index]", option.get_wordlist_name ());
          else
            printf ("s");
          printf (";\n"
                  "%*s    }\n",
                  indent, "");
          if (option[LENTABLE])
            {
              indent -= 4;
              printf ("%*s    }\n", indent, "");
            }
          if (_total_duplicates > 0)
            {
              printf ("%*s  else if (index < -%sTOTAL_KEYWORDS)\n"
                      "%*s    {\n"
                      "%*s      %sint offset = - 1 - %sTOTAL_KEYWORDS - index;\n",
                      indent, "", option.get_constants_prefix (), indent, "",
                      indent, "", register_scs, option.get_constants_prefix ());
              if (option[LENTABLE])
                printf ("%*s      %s%s%s *lengthptr = &%s[%sTOTAL_KEYWORDS + lookup[offset]];\n",
                        indent, "", register_scs, const_always, smallest_integral_type (_max_key_len),
                        option.get_lengthtable_name (), option.get_constants_prefix ());
              printf ("%*s      %s",
                      indent, "", register_scs);
              output_const_type (const_readonly_array, _wordlist_eltype);
              printf ("*wordptr = &%s[%sTOTAL_KEYWORDS + lookup[offset]];\n",
                      option.get_wordlist_name (), option.get_constants_prefix ());
              printf ("%*s      %s",
                      indent, "", register_scs);
              output_const_type (const_readonly_array, _wordlist_eltype);
              printf ("*wordendptr = wordptr + -lookup[offset + 1];\n\n");
              printf ("%*s      while (wordptr < wordendptr)\n"
                      "%*s        {\n",
                      indent, "", indent, "");
              if (option[LENTABLE])
                {
                  printf ("%*s          if (len == *lengthptr)\n"
                          "%*s            {\n",
                          indent, "", indent, "");
                  indent += 4;
                }
              printf ("%*s          %s%schar *s = ",
                      indent, "", register_scs, const_always);
              if (option[TYPE])
                printf ("wordptr->%s", option.get_slot_name ());
              else
                printf ("*wordptr");
              if (option[SHAREDLIB])
                printf (" + %s",
                        option.get_stringpool_name ());
              printf (";\n\n"
                      "%*s          if (",
                      indent, "");
              comparison.output_comparison (Output_Expr1 ("str"), Output_Expr1 ("s"));
              printf (")\n"
                      "%*s            return %s;\n",
                      indent, "",
                      option[TYPE] ? "wordptr" : "s");
              if (option[LENTABLE])
                {
                  indent -= 4;
                  printf ("%*s            }\n",
                          indent, "");
                }
              if (option[LENTABLE])
                printf ("%*s          lengthptr++;\n",
                        indent, "");
              printf ("%*s          wordptr++;\n"
                      "%*s        }\n"
                      "%*s    }\n",
                      indent, "", indent, "", indent, "");
            }
          printf ("%*s}\n",
                  indent, "");
        }
      else
        {
          int indent = 8;
          if (option[LENTABLE])
            {
              printf ("%*sif (len == %s[key])\n",
                      indent, "", option.get_lengthtable_name ());
              indent += 2;
            }

          if (option[SHAREDLIB])
            {
              if (!option[LENTABLE])
                {
                  printf ("%*s{\n"
                          "%*s  %sint o = %s[key]",
                          indent, "",
                          indent, "", register_scs,
                          option.get_wordlist_name ());
                  if (option[TYPE])
                    printf (".%s", option.get_slot_name ());
                  printf (";\n"
                          "%*s  if (o >= 0)\n"
                          "%*s    {\n",
                          indent, "",
                          indent, "");
                  indent += 4;
                  printf ("%*s  %s%schar *s = o",
                          indent, "", register_scs, const_always);
                }
              else
                {
                  /* No need for the (o >= 0) test, because the
                     (len == lengthtable[key]) test already guarantees that
                     key points to nonempty table entry.  */
                  printf ("%*s{\n"
                          "%*s  %s%schar *s = %s[key]",
                          indent, "",
                          indent, "", register_scs, const_always,
                          option.get_wordlist_name ());
                  if (option[TYPE])
                    printf (".%s", option.get_slot_name ());
                }
              printf (" + %s",
                      option.get_stringpool_name ());
            }
          else
            {
              printf ("%*s{\n"
                      "%*s  %s%schar *s = %s[key]",
                      indent, "",
                      indent, "", register_scs, const_always,
                      option.get_wordlist_name ());
              if (option[TYPE])
                printf (".%s", option.get_slot_name ());
            }

          printf (";\n\n"
                  "%*s  if (",
                  indent, "");
          if (!option[SHAREDLIB] && option[NULLSTRINGS])
            printf ("s && ");
          comparison.output_comparison (Output_Expr1 ("str"), Output_Expr1 ("s"));
          printf (")\n"
                  "%*s    return ",
                  indent, "");
          if (option[TYPE])
            printf ("&%s[key]", option.get_wordlist_name ());
          else
            printf ("s");
          printf (";\n");
          if (option[SHAREDLIB] && !option[LENTABLE])
            {
              indent -= 4;
              printf ("%*s    }\n",
                      indent, "");
            }
          printf ("%*s}\n",
                  indent, "");
        }
    }
  printf ("    }\n"
          "  return %s;\n",
          null_expression);

  delete[] null_expression;
}

/* Generates C code for the lookup function.  */

void
Output::output_lookup_function () const
{
  /* Output the function's head.  */
  /* We don't declare the lookup function 'static' because we cannot make
     assumptions about the compilation units of the user.
     Since we don't make it 'static', it makes no sense to declare it 'inline',
     because non-static inline functions must not reference static functions or
     variables, see ISO C 99 section 6.7.4.(3).  */

  printf ("%s%s\n",
          const_for_struct, _return_type);
  if (option[CPLUSPLUS])
    printf ("%s::", option.get_class_name ());
  printf ("%s ", option.get_function_name ());
  printf (option[KRC] ?
                 "(str, len)\n"
            "     %schar *str;\n"
            "     %ssize_t len;\n" :
          option[C] ?
                 "(str, len)\n"
            "     %sconst char *str;\n"
            "     %ssize_t len;\n" :
          option[ANSIC] | option[CPLUSPLUS] ?
                 "(%sconst char *str, %ssize_t len)\n" :
          "",
          register_scs, register_scs);

  /* Output the function's body.  */
  printf ("{\n");

  if (option[ENUM] && !option[GLOBAL])
    {
      Output_Enum style ("  ");
      output_constants (style);
    }

  if (option[SHAREDLIB] && !(option[GLOBAL] || option[TYPE]))
    output_lookup_pools ();
  if (!option[GLOBAL])
    output_lookup_tables ();

  if (option[LENTABLE])
    output_lookup_function_body (Output_Compare_Memcmp ());
  else
    {
      if (option[COMP])
        output_lookup_function_body (Output_Compare_Strncmp ());
      else
        output_lookup_function_body (Output_Compare_Strcmp ());
    }

  printf ("}\n");
}

/* ------------------------------------------------------------------------- */

/* Generates the hash function and the key word recognizer function
   based upon the user's Options.  */

void
Output::output ()
{
  compute_min_max ();

  if (option[CPLUSPLUS])
    /* The 'register' keyword is removed from C++17.
       See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4340  */
    register_scs = "";
  else
    register_scs = "register ";

  if (option[C] | option[ANSIC] | option[CPLUSPLUS])
    {
      const_always = "const ";
      const_readonly_array = (option[CONST] ? "const " : "");
      const_for_struct = ((option[CONST] && option[TYPE]) ? "const " : "");
    }
  else
    {
      const_always = "";
      const_readonly_array = "";
      const_for_struct = "";
    }

  if (!option[TYPE])
    {
      _return_type = (const_always[0] ? "const char *" : "char *");
      _struct_tag = (const_always[0] ? "const char *" : "char *");
    }

  _wordlist_eltype = (option[SHAREDLIB] && !option[TYPE] ? "int" : _struct_tag);

  printf ("/* ");
  if (option[KRC])
    printf ("KR-C");
  else if (option[C])
    printf ("C");
  else if (option[ANSIC])
    printf ("ANSI-C");
  else if (option[CPLUSPLUS])
    printf ("C++");
  printf (" code produced by gperf version %s */\n", version_string);
  option.print_options ();
  printf ("\n");
  if (!option[POSITIONS])
    {
      printf ("/* Computed positions: -k'");
      _key_positions.print();
      printf ("' */\n");
    }
  printf ("\n");

  if (_charset_dependent
      && (_key_positions.get_size() > 0 || option[UPPERLOWER]))
    {
      /* The generated tables assume that the execution character set is
         based on ISO-646, not EBCDIC.  */
      printf ("#if !((' ' == 32) && ('!' == 33) && ('\"' == 34) && ('#' == 35) \\\n"
              "      && ('%%' == 37) && ('&' == 38) && ('\\'' == 39) && ('(' == 40) \\\n"
              "      && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \\\n"
              "      && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \\\n"
              "      && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \\\n"
              "      && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \\\n"
              "      && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \\\n"
              "      && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \\\n"
              "      && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \\\n"
              "      && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \\\n"
              "      && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \\\n"
              "      && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \\\n"
              "      && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \\\n"
              "      && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \\\n"
              "      && ('Z' == 90) && ('[' == 91) && ('\\\\' == 92) && (']' == 93) \\\n"
              "      && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \\\n"
              "      && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \\\n"
              "      && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \\\n"
              "      && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \\\n"
              "      && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \\\n"
              "      && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \\\n"
              "      && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \\\n"
              "      && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))\n"
              "/* The character set is not based on ISO-646.  */\n");
      printf ("%s \"gperf generated tables don't work with this execution character set. Please report a bug to <bug-gperf@gnu.org>.\"\n", option[KRC] || option[C] ? "error" : "#error");
      printf ("#endif\n\n");
    }

  if (_verbatim_declarations < _verbatim_declarations_end)
    {
      output_line_directive (_verbatim_declarations_lineno);
      fwrite (_verbatim_declarations, 1,
              _verbatim_declarations_end - _verbatim_declarations, stdout);
    }

  if (option[TYPE] && !option[NOTYPE]) /* Output type declaration now, reference it later on.... */
    {
      output_line_directive (_struct_decl_lineno);
      printf ("%s\n", _struct_decl);
    }

  if (option[INCLUDE])
    printf ("#include <string.h>\n"); /* Declare strlen(), strcmp(), strncmp(). */

  if (!option[ENUM])
    {
      Output_Defines style;
      output_constants (style);
    }
  else if (option[GLOBAL])
    {
      Output_Enum style ("");
      output_constants (style);
    }

  printf ("/* maximum key range = %d, duplicates = %d */\n\n",
          _max_hash_value - _min_hash_value + 1, _total_duplicates);

  if (option[UPPERLOWER])
    {
      #if USE_DOWNCASE_TABLE
      output_upperlower_table ();
      #endif

      if (option[LENTABLE])
        output_upperlower_memcmp ();
      else
        {
          if (option[COMP])
            output_upperlower_strncmp ();
          else
            output_upperlower_strcmp ();
        }
    }

  if (option[CPLUSPLUS])
    printf ("class %s\n"
            "{\n"
            "private:\n"
            "  static inline unsigned int %s (const char *str, size_t len);\n"
            "public:\n"
            "  static %s%s%s (const char *str, size_t len);\n"
            "};\n"
            "\n",
            option.get_class_name (), option.get_hash_name (),
            const_for_struct, _return_type, option.get_function_name ());

  output_hash_function ();

  if (option[SHAREDLIB] && (option[GLOBAL] || option[TYPE]))
    output_lookup_pools ();
  if (option[GLOBAL])
    output_lookup_tables ();

  output_lookup_function ();

  if (_verbatim_code < _verbatim_code_end)
    {
      output_line_directive (_verbatim_code_lineno);
      fwrite (_verbatim_code, 1, _verbatim_code_end - _verbatim_code, stdout);
    }

  fflush (stdout);
}
