/*
 * Copyright (c) 2021-2025 Symas Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following disclaimer
 *   in the documentation and/or other materials provided with the
 *   distribution.
 * * Neither the name of the Symas Corporation nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
// This file is included only by parse.y

#include <map>

/*
 * Intrinsics
 * types are:
 *   A Alphabetic
 *   D DBCS
 *   I Integer
 *   K Keyword
 *   N Numeric
 *   O Other
 *   U National
 *   8 UTF-8
 *   X Alphanumeric
 *   n variadic
 * We use just A, I, N, or X, choosing the most general for each parameter.
 */
static const function_descr_t function_descrs[] = {
   {         ABS,                          "ABS",
      "__gg__abs",                         "N",   {}, FldNumericBin5 },
   {         ACOS,                         "ACOS",
      "__gg__acos",                        "N",   {}, FldNumericBin5 },
   {         ANNUITY,                      "ANNUITY",
      "__gg__annuity",                     "NI",  {}, FldNumericBin5 },
   {         ASIN,                         "ASIN",
      "__gg__asin",                        "N",   {}, FldNumericBin5 },
   {         ATAN,                         "ATAN",
      "__gg__atan",                        "N",   {}, FldNumericBin5 },
   {         BASECONVERT,                         "BASECONVERT",
      "__gg__baseconvert",                        "XII",   {}, FldNumericBin5 },
   {         BIT_OF,                       "BIT-OF",
      "__gg__bit_of",                      "X",   {}, FldAlphanumeric },
   {         BIT_TO_CHAR,                  "BIT-TO-CHAR",
      "__gg__bit_to_char",                 "X",   {}, FldAlphanumeric },
   // BOOLEAN-OF-INTEGER requires FldBoolean
   {         BOOLEAN_OF_INTEGER,                         "BOOLEAN-OF-INTEGER",
      "__gg__boolean_of_integer",                        "II",   {}, FldNumericBin5 },
   {         BYTE_LENGTH,                  "BYTE-LENGTH",
       "__gg__byte_length",                "X",   {}, FldNumericBin5 },
   {         CHAR,                         "CHAR",
      "__gg__char",                        "I",   {}, FldAlphanumeric },
   {         CHAR_NATIONAL,                         "CHAR-NATIONAL",
      "__gg__char_national",                        "I",   {}, FldAlphanumeric },
   {         COMBINED_DATETIME,            "COMBINED-DATETIME",
      "__gg__combined_datetime",           "IN",  {}, FldNumericBin5 },
   {         CONCAT,                       "CONCAT",
      "__gg__concat",                      "n",   {}, FldAlphanumeric },
   {         CONVERT,                          "CONVERT",
      "__gg__convert",                         "XII",   {}, FldAlphanumeric },
   {         COS,                          "COS",
      "__gg__cos",                         "N",   {}, FldNumericBin5 },
   {         CURRENT_DATE,                 "CURRENT-DATE",
      "__gg__current_date",                 "",   {}, FldAlphanumeric },
   {         DATE_OF_INTEGER,              "DATE-OF-INTEGER",
      "__gg__date_of_integer",             "I",   {}, FldNumericBin5 },
   {         DATE_TO_YYYYMMDD,             "DATE-TO-YYYYMMDD",
      "__gg__date_to_yyyymmdd",            "III",  {}, FldNumericBin5 },
   {         DAY_OF_INTEGER,               "DAY-OF-INTEGER",
      "__gg__day_of_integer",              "I",   {}, FldNumericBin5 },
   {         DAY_TO_YYYYDDD,               "DAY-TO-YYYYDDD",
      "__gg__day_to_yyyyddd",              "III",  {}, FldNumericBin5 },
   {         DISPLAY_OF,                   "DISPLAY-OF",
      "__gg__display_of",                  "UUI", {}, FldAlphanumeric },
   {         E,                            "E",
      "__gg_e",                            "",   {}, FldNumericBin5 },

   {         EXCEPTION_FILE,               "EXCEPTION-FILE",
      "__gg__func_exception_file",         "",   {}, FldAlphanumeric },
   {         EXCEPTION_FILE_N,             "EXCEPTION-FILE-N",
      "__gg__func_exception_file_n",       "",   {}, FldAlphanumeric },
   {         EXCEPTION_LOCATION,           "EXCEPTION-LOCATION",
      "__gg__func_exception_location",     "",   {}, FldAlphanumeric },
   {         EXCEPTION_LOCATION_N,         "EXCEPTION-LOCATION-N",
      "__gg__func_exception_location_n",   "",   {}, FldAlphanumeric },
   {         EXCEPTION_STATEMENT,          "EXCEPTION-STATEMENT",
      "__gg__func_exception_statement",    "",   {}, FldAlphanumeric },
   {         EXCEPTION_STATUS,             "EXCEPTION-STATUS",
      "__gg__func_exception_status",       "",   {}, FldAlphanumeric },

   {         EXP,                          "EXP",
      "__gg__exp",                         "N",   {}, FldNumericBin5 },
   {         EXP10,                        "EXP10",
      "__gg__exp10",                       "N",   {}, FldNumericBin5 },
   {         FACTORIAL,                    "FACTORIAL",
      "__gg__factorial",                   "I",   {}, FldNumericBin5 },
   {         FIND_STRING,                    "FIND-STRING",
      "__gg__find_string",                   "AXI",   {}, FldNumericBin5 },
   {         FORMATTED_CURRENT_DATE,       "FORMATTED-CURRENT-DATE",
      "__gg__formatted_current_date",      "X",   {}, FldAlphanumeric },
   {         FORMATTED_DATE,               "FORMATTED-DATE",
      "__gg__formatted_date",              "XX",  {}, FldAlphanumeric },
   {         FORMATTED_DATETIME,           "FORMATTED-DATETIME",
      "__gg__formatted_datetime",          "XINI", {}, FldAlphanumeric },
   {         FORMATTED_TIME,               "FORMATTED-TIME",
      "__gg__formatted_time",              "INI", {}, FldNumericBin5 },
   {         FRACTION_PART,                "FRACTION-PART",
      "__gg__fraction_part",               "N",   {}, FldNumericBin5 },
   {         HEX_OF,                       "HEX-OF",
      "__gg__hex_of",                      "X",   {}, FldAlphanumeric },
   {         HEX_TO_CHAR,                  "HEX-TO-CHAR",
      "__gg__hex_to_char",                 "X",   {}, FldAlphanumeric },
   {         HIGHEST_ALGEBRAIC,            "HIGHEST-ALGEBRAIC",
      "__gg__highest_algebraic",           "N",   {}, FldNumericBin5 },
   {         INTEGER,                      "INTEGER",
      "__gg__integer",                     "N",   {}, FldNumericBin5 },
   // requires FldBoolean
   {         INTEGER_OF_BOOLEAN,                      "INTEGER-OF-BOOLEAN",
      "__gg__integer_of_boolean",                     "B",   {}, FldNumericBin5 },
   {         INTEGER_OF_DATE,              "INTEGER-OF-DATE",
      "__gg__integer_of_date",             "I",   {}, FldNumericBin5 },
   {         INTEGER_OF_DAY,               "INTEGER-OF-DAY",
      "__gg__integer_of_day",              "I",   {}, FldNumericBin5 },
   {         INTEGER_OF_FORMATTED_DATE,    "INTEGER-OF-FORMATTED-DATE",
      "__gg__integer_of_formatted_date",   "XX",  {}, FldAlphanumeric },
   {         INTEGER_PART,                 "INTEGER-PART",
      "__gg__integer_part",                "N",   {}, FldNumericBin5 },
   {         LENGTH,                       "LENGTH",
      "__gg__length",                      "X",   {}, FldNumericBin5 },
   {         LOCALE_COMPARE,               "LOCALE-COMPARE",
      "__gg__locale_compare",              "XXX",   {}, FldNumericBin5 },
   {         LOCALE_DATE,                  "LOCALE-DATE",
      "__gg__locale_date",                 "XX",   {}, FldNumericBin5 },
   {         LOCALE_TIME,                  "LOCALE-TIME",
      "__gg__locale_time",                 "XX",   {}, FldNumericBin5 },
   {         LOCALE_TIME_FROM_SECONDS,     "LOCALE-TIME-FROM-SECONDS",
      "__gg__locale_time_from_seconds",    "NX",   {}, FldNumericBin5 },

   {         LOG,                          "LOG",
      "__gg__log",                         "N",   {}, FldNumericBin5 },
   {         LOG10,                        "LOG10",
      "__gg__log10",                       "N",   {}, FldNumericBin5 },
   {         LOWER_CASE,                   "LOWER-CASE",
      "__gg__lower_case",                  "X",   {}, FldAlphanumeric },
   {         LOWEST_ALGEBRAIC,             "LOWEST-ALGEBRAIC",
      "__gg__lowest_algebraic",            "N",   {}, FldNumericBin5 },

   {         MAXX,                          "MAX",
      "__gg__max",                         "n",   {}, FldAlphanumeric },
   {         MEAN,                         "MEAN",
      "__gg__mean",                        "n",   {}, FldNumericBin5 },
   {         MEDIAN,                       "MEDIAN",
      "__gg__median",                      "n",   {}, FldNumericBin5 },
   {         MIDRANGE,                     "MIDRANGE",
      "__gg__midrange",                    "n",   {}, FldNumericBin5 },
   {         MINN,                          "MIN",
      "__gg__min",                         "n",   {}, FldAlphanumeric },
   {         MOD,                          "MOD",
      "__gg__mod",                         "IN",  {}, FldNumericBin5 },
   {         MODULE_NAME,                          "MODULE-NAME",
      "__gg__module_name",                         "I",  {}, FldAlphanumeric },
   {         NATIONAL_OF,                  "NATIONAL-OF",
      "__gg__national_of",                 "XX",  {}, FldAlphanumeric },
   {         NUMVAL,                       "NUMVAL",
      "__gg__numval",                      "X",   {}, FldNumericBin5 },
   {         NUMVAL_C,                     "NUMVAL-C",
      "__gg__numval_c",                    "XXU", {}, FldNumericBin5 },
   {         NUMVAL_F,                     "NUMVAL-F",
      "__gg__numval_f",                    "X",   {}, FldNumericBin5 },
   {         ORD,                          "ORD",
      "__gg__ord",                         "X",   {}, FldNumericBin5 },
   {         ORD_MAX,                      "ORD-MAX",
      "__gg__ord_max",                     "n",   {}, FldNumericBin5 },
   {         ORD_MIN,                      "ORD-MIN",
      "__gg__ord_min",                     "n",   {}, FldNumericBin5    },
   {         PI,                           "PI",
      "__gg__pi",                          "",   {}, FldNumericBin5 },
   {         PRESENT_VALUE,                "PRESENT-VALUE",
      "__gg__present_value",               "n",   {}, FldNumericBin5    },
   {         RANDOM,                       "RANDOM",
      "__gg__random",                      "I",   {}, FldNumericBin5 },
   {         RANGE,                        "RANGE",
      "__gg__range",                       "n",   {}, FldNumericBin5    },
   {         REM,                          "REM",
      "__gg__rem",                         "NN",  {}, FldNumericBin5 },
   {         REVERSE,                      "REVERSE",
      "__gg__reverse",                     "X",   {}, FldAlphanumeric },
   {         SECONDS_FROM_FORMATTED_TIME,  "SECONDS-FROM-FORMATTED-TIME",
      "__gg__seconds_from_formatted_time", "XX",  {}, FldAlphanumeric },
   {         SECONDS_PAST_MIDNIGHT,        "SECONDS_PAST_MIDNIGHT",
      "__gg__seconds_past_midnight",       "",    {}, FldAlphanumeric },
   {         SIGN,                         "SIGN",
      "__gg__sign",                        "N",   {}, FldNumericBin5 },
   {         SIN,                          "SIN",
      "__gg__sin",                         "N",   {}, FldNumericBin5 },
   {         SMALLEST_ALGEBRAIC,                          "SMALLEST-ALGEBRAIC",
      "__gg__smallest_algebraic",                         "N",   {}, FldNumericBin5 },
   {         SQRT,                         "SQRT",
      "__gg__sqrt",                        "N",   {}, FldNumericBin5 },
   {         STANDARD_COMPARE,                          "STANDARD-COMPARE",
      "__gg__standard_compare",                         "XXXI",   {}, FldAlphanumeric },
   {         STANDARD_DEVIATION,           "STANDARD-DEVIATION",
      "__gg__standard_deviation",          "n",   {}, FldNumericBin5 },
   {         SUBSTITUTE,                          "SUBSTITUTE",
      "__gg__substitute",                         "XXX",   {}, FldAlphanumeric },
   {         SUM,                          "SUM",
      "__gg__sum",                         "n",   {}, FldNumericBin5    },
   {         TAN,                          "TAN",
      "__gg__tan",                         "N",   {}, FldNumericBin5 },
   {         TEST_DATE_YYYYMMDD,           "TEST-DATE-YYYYMMDD",
      "__gg__test_date_yyyymmdd",          "I",   {}, FldNumericBin5 },
   {         TEST_DAY_YYYYDDD,             "TEST-DAY-YYYYDDD",
      "__gg__test_day_yyyyddd",            "I",   {}, FldNumericBin5 },
   {         TEST_FORMATTED_DATETIME,      "TEST-FORMATTED-DATETIME",
      "__gg__test_formatted_datetime",     "XX",  {}, FldNumericBin5 },
   {         TEST_NUMVAL,                  "TEST-NUMVAL",
      "__gg__test_numval",                 "X",   {}, FldNumericBin5 },
   {         TEST_NUMVAL_C,                "TEST-NUMVAL-C",
      "__gg__test_numval_c",               "XXU", {}, FldNumericBin5 },
   {         TEST_NUMVAL_F,                "TEST-NUMVAL-F",
      "__gg__test_numval_f",               "X",   {}, FldNumericBin5 },
   {         TRIM,                         "TRIM",
      "__gg__trim",                        "XI",  {}, FldNumericBin5 },
   {         ULENGTH,                      "ULENGTH",
      "__gg__ulength",                     "X",   {}, FldAlphanumeric },
   {         UPOS,                         "UPOS",
      "__gg__upos",                        "XI",  {}, FldAlphanumeric },
   {         UPPER_CASE,                   "UPPER-CASE",
      "__gg__upper_case",                  "X",   {}, FldAlphanumeric },
   {         USUBSTR,                      "USUBSTR",
      "__gg__usubstr",                     "XII", {}, FldAlphanumeric },
   {         USUPPLEMENTARY,               "USUPPLEMENTARY",
      "__gg__usupplementary",              "X",   {}, FldAlphanumeric },
   {         UUID4,                        "UUID4",
       "__gg_uuid4",                        "",   {}, FldAlphanumeric },
   {         UVALID,                       "UVALID",
      "__gg__uvalid",                      "X",   {}, FldAlphanumeric },
   {         UWIDTH,                       "UWIDTH",
      "__gg__uwidth",                      "XI",  {}, FldAlphanumeric },
   {         VARIANCE,                     "VARIANCE",
      "__gg__variance",                    "n",   {}, FldNumericBin5 },
   {         WHEN_COMPILED,                "WHEN-COMPILED",
      "__gg__when_compiled",               "",    {}, FldAlphanumeric },
   {         YEAR_TO_YYYY,                 "YEAR-TO-YYYY",
      "__gg__year_to_yyyy",                "III",  {}, FldNumericBin5 },
  };

static const
function_descr_t *function_descrs_end = function_descrs + COUNT_OF(function_descrs);

class cname_cmp {
  const char *cname;
 public:
  cname_cmp( const char *cname ) : cname(cname) {}

  bool operator()( const function_descr_t& descr ) {
    return strlen(cname) == strlen(descr.cname) &&
      0 == strcmp(cname, descr.cname);
  }
  bool operator()( const char that[] ) {
    return strlen(cname) == strlen(that) &&
      0 == strcmp(cname, that);
  }
};

/*
 * For variadic intrinsic functions, ensure all parameters are commensurate.
 * Return pointer in 1st inconsistent parameter type.
 * Return NULL to indicate success.
 */
static cbl_refer_t *
intrinsic_inconsistent_parameter( size_t n, cbl_refer_t *args ) {
  class commensurate_type {
    cbl_refer_t first;
   public:
    commensurate_type( const cbl_refer_t& first ) : first(first) {}
    bool operator()( cbl_refer_t& arg )  const {
      return is_numeric(first.field) == is_numeric(arg.field);
    }
  };

  auto p = std::find_if_not(args, args + n, commensurate_type(args[0]));
  return p == args + n? NULL : p;
}

static cbl_field_type_t
intrinsic_return_type( int token ) {
  auto p = std::find_if( function_descrs,
                         function_descrs_end,
                         [token]( const auto& descr ) {
                           return token == descr.token;
                         } );
  return p == function_descrs_end? FldAlphanumeric : p->ret_type;
}

static const char *
intrinsic_cname( int token ) {
  auto p = std::find_if( function_descrs,
                         function_descrs_end,
                         [token]( const auto& descr ) {
                           return token == descr.token;
                         } );
  return p == function_descrs_end?  NULL :  p->cname;
}

const char *
intrinsic_function_name( int token ) {
  auto p = std::find_if( function_descrs,
                         function_descrs_end,
                         [token]( const auto& descr ) {
                           return token == descr.token;
                         } );
  return p == function_descrs_end?  NULL :  p->name;
}

/*
 * Provide supplied function parameters.
 * Return index to 1st invalid parameter type.
 * Return N to indicate success.
 */
static size_t
intrinsic_invalid_parameter( int token,
                             const std::vector<cbl_refer_t>& args )
{
  auto p = std::find_if( function_descrs,
                         function_descrs_end,
                         [token]( const auto& descr ) {
                           return token == descr.token;
                         } );
  if( p == function_descrs_end ) {
    cbl_internal_error( "%s: intrinsic function  %s not found",
                        __func__, keyword_str(token) );
  }

  gcc_assert(!args.empty());
  gcc_assert(p < function_descrs_end);

  const function_descr_t& descr = *p;

  size_t i = 0;
  for( auto arg : args ) {
    if( arg.field == NULL ) {
      i++;
      continue;
    }
    assert(i < strlen(descr.types));

    switch(descr.types[i]) {
    case 'A' : //Alphabetic
    case 'I' : //Integer
    case 'N' : //Numeric
    case 'X' : //Alphanumeric
      break;
    case 'n' : //variadic
      return args.size();
      break;
    case 'D' : //DBCS
    case 'K' : //Keyword
    case 'O' : //Other
    case 'U' : //National
    case '8' : //UTF-8
    default:
      cbl_internal_error( "%s: invalid function descr type '%c'",
           __func__, descr.types[i]);
    }

    static std::map<char, const char*> typenames
      {
        { 'A', "Alphabetic" },
        { 'I', "Integer" },
        { 'N', "Numeric" },
        { 'X', "Alphanumeric" },        
      };

    switch( arg.field->type ) {
    case FldInvalid:
    case FldClass:
    case FldConditional:
    case FldForward:
    case FldIndex:
      yyerror("%s: field '%s' (%s) invalid for %s parameter",
               descr.name,
               arg.field->name, cbl_field_type_str(arg.field->type),
               typenames[descr.types[i]]);
      return i;
      break;
    case FldGroup:
    default:
      break;
    }

    if( is_numeric(arg.field) || is_integer_literal(arg.field)) {
      if( strchr("A", descr.types[i]) != NULL ) {
        yyerror("%s: numeric field '%s' (%s) invalid for %s parameter",
                 descr.name,
                 arg.field->name, cbl_field_type_str(arg.field->type),
                 typenames[descr.types[i]]);
        return i;
      }
    } else { // string field
      if( strchr("IN", descr.types[i]) != NULL ) {
        if( data_category_of(arg.field) == data_alphabetic_e ) {
          yyerror("%s: non-numeric field '%s' (%s) invalid for %s parameter",
                   descr.name,
                   arg.field->name, cbl_field_type_str(arg.field->type),
                   typenames[descr.types[i]]);
          return i;
        }
      }
    }
    i++;
  } // end loop

  return args.size();
}

/*
 * Functions used by code gen
 */

size_t
intrinsic_parameter_count( const char cname[] ) {
  const function_descr_t *descr = std::find_if(function_descrs,
                                               function_descrs_end, cname_cmp(cname));
  return descr == function_descrs_end || descr->types[0] == 'n'?
    size_t(-1) : strlen(descr->types);
}

#if 0
static int
yyreport_syntax_error (const yypcontext_t *ctx)
{
  int res = 0;
  YYLOCATION_PRINT (stderr, yypcontext_location (ctx));
  fprintf (stderr, ": syntax error");
  // Report the tokens expected at this point.
  {
    enum { TOKENMAX = 5 };
    yysymbol_kind_t expected[TOKENMAX];
    int n = yypcontext_expected_tokens (ctx, expected, TOKENMAX);
    if (n < 0)
      // Forward errors to yyparse.
      res = n;
    else
      for (int i = 0; i < n; ++i)
        fprintf (stderr, "%s %s",
                 i == 0 ? ": expected" : " or", yysymbol_name (expected[i]));
  }
  // Report the unexpected token.
  {
    yysymbol_kind_t lookahead = yypcontext_token (ctx);
    if (lookahead != YYSYMBOL_YYEMPTY)
      fprintf (stderr, " before %s", yysymbol_name (lookahead));
  }
  fprintf (stderr, "\n");
  return res;
}
#endif