# Copyright (c) 1998 Lawrence Livermore National Security, LLC and other
# HYPRE Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

#################################################################################
#
# Generate code for multiprecision
#
# The script parses a prototype info file (generated by 'gen_proto_info.sh') and
# generates files 'outc' and 'outh' containing function implementations and
# prototypes for each line of prototype info.
#
# Usage:   awk -v gen="<option>" -v outc="<c-file>" -v outh="<h-file>" -f <this-file> <prototype-info-file>
# Example: awk -v gen="pre" -v outc="mup_pre.c" -v outh="mup_pre.h" -f gen_code.awk mup.pre.proto
#
#################################################################################

BEGIN {
   FS=" , "
}

{
   fret  = $1
   fdef  = $2
   tab   = "   "

   ParseArgs()

   if (gen == "fixed")
   {
      GenFixed()
   }
   else if (gen == "functions")
   {
      GenFunctions()
   }
   else if (gen == "pre")
   {
      GenPre()
   }
}

#################################################################################
#
# Modify arguments for real types
#
#################################################################################

function SubFloat(args)
{
   gsub(/(HYPRE_Real|HYPRE_Complex)/, "hypre_float", args)
   return args
}

function SubDouble(args)
{
   gsub(/(HYPRE_Real|HYPRE_Complex)/, "hypre_double", args)
   return args
}

function SubLongDouble(args)
{
   gsub(/(HYPRE_Real|HYPRE_Complex)/, "hypre_long_double", args)
   return args
}

function SubMUP(args)
{
   # First replace HYPRE_Real* and HYPRE_Complex* with void*
   gsub(/(HYPRE_Real|HYPRE_Complex)[[:blank:]]*[*]+/, "void *", args)
   gsub(/(HYPRE_Real|HYPRE_Complex)/, "hypre_long_double", args)
   return args
}

function AddCast(pargs, cargs,          n, i, pa, ca, cast, cvar)
{
   # The number of arguments in pargs and cargs must be the same and correlated
   n = split(pargs, pa, ",")
   n = split(cargs, ca, ",")
   cargs_cst = ""
   for(i = 1; i <= n; i++)
   {
      # Initialize cast and set the call variable (remove leading and trailing spaces)
      cast = ""
      cvar = ca[i]
      sub(/^[[:blank:]]*/, "", cvar); sub(/[[:blank:]]*$/, "", cvar)
      if (pa[i] ~ /(HYPRE_Real|HYPRE_Complex)/)
      {
         # Set the cast (extract the type, replace leading and trailing spaces with parenthesis)
         cast = pa[i]
         gsub(cvar, "", cast)
         sub(/^[[:blank:]]*/, "(", cast); sub(/[[:blank:]]*$/, ")", cast)
      }
      # Update the call string with additional cast (if any)
      if(i < n)
      {
         cargs_cst = cargs_cst cast cvar ","
      }
      else
      {
         cargs_cst = cargs_cst cast cvar
      }
   }

   return cargs_cst
}

function Format(args)
{
   # Ensure that commas only have a trailing space
   gsub(/[[:blank:]]*[,][[:blank:]]*/, ", ", args)
   # Ensure that the arg list has only one leading and trailing space
   sub(/^[[:blank:]]*/, " ", args); sub(/[[:blank:]]*$/, " ", args)

   return args
}

#################################################################################
#
# Append code for fixed precision functions to files 'outc' and 'outh'.
#
#################################################################################

function GenFixed()
{
   pargs     = Format(pargs)
   pargs_flt = SubFloat(pargs)
   pargs_dbl = SubDouble(pargs)
   pargs_ldb = SubLongDouble(pargs)

   cargs     = Format(cargs)

   # Prototypes
   print fret"\n"fdef"_flt("pargs_flt");"                                                   >> outh
   print fret"\n"fdef"_dbl("pargs_dbl");"                                                   >> outh
   print fret"\n"fdef"_long_dbl("pargs_ldb");\n"                                            >> outh

   # Implementation
   print "/*--------------------------------------------------------------------------*/\n" >> outc
   print fret"\n"fdef"("pargs")"                                                            >> outc
   print "{"                                                                                >> outc
   print tab "return HYPRE_CURRENTPRECISION_FUNC("fdef")("cargs");"                         >> outc
   print "}\n"                                                                              >> outc
}

#################################################################################
#
# Append code for multiprecision functions to files 'outc' and 'outh'.
#
#################################################################################

function GenFunctions()
{
   pargs     = Format(pargs)
   pargs_flt = SubFloat(pargs)
   pargs_dbl = SubDouble(pargs)
   pargs_ldb = SubLongDouble(pargs)
   pargs_mup = SubMUP(pargs)

   if (nargs == 0)
   {
      cargs_pre = "precision"
   }
   else
   {
      cargs_pre = "precision," cargs
   }
   cargs_pre = Format(cargs_pre)

   # Prototypes
   print fret"\n"fdef"_flt("pargs_flt");"                                                   >> outh
   print fret"\n"fdef"_dbl("pargs_dbl");"                                                   >> outh
   print fret"\n"fdef"_long_dbl("pargs_ldb");"                                              >> outh
   print fret"\n"fdef"("pargs_mup");\n"                                                     >> outh

   # Implementation
   print "/*--------------------------------------------------------------------------*/\n" >> outc
   print fret"\n"fdef"("pargs_mup")"                                                        >> outc
   print "{"                                                                                >> outc
   print tab "HYPRE_Precision precision = hypre_GlobalPrecision();"                         >> outc
   print tab "return "fdef"_pre("cargs_pre");"                                              >> outc
   print "}\n"                                                                              >> outc
}

#################################################################################
#
# Append code for multiprecision '_pre' functions to files 'outc' and 'outh'.
#
#################################################################################

function GenPre()
{
   if (nargs == 0)
   {
      pargs_pre = "HYPRE_Precision precision"
   }
   else
   {
      pargs_pre = "HYPRE_Precision precision," pargs
   }
   pargs_pre = Format(pargs_pre)
   pargs_pre = SubMUP(pargs_pre)

   cargs_cst = AddCast(pargs, cargs)
   cargs_cst = Format(cargs_cst)
   cargs_flt = SubFloat(cargs_cst)
   cargs_dbl = SubDouble(cargs_cst)
   cargs_ldb = SubLongDouble(cargs_cst)

   # Prototypes
   print fret"\n"fdef"_pre("pargs_pre");\n"                                                 >> outh

   # Implementation
   print "/*--------------------------------------------------------------------------*/\n" >> outc
   print fret"\n"fdef"_pre("pargs_pre")"                                                    >> outc
   print "{"                                                                                >> outc
   print tab "switch (precision)"                                                           >> outc
   print tab "{"                                                                            >> outc
   print tab tab "case HYPRE_REAL_SINGLE:"                                                  >> outc
   print tab tab tab "return "fdef"_flt("cargs_flt");"                                      >> outc
   print tab tab "case HYPRE_REAL_DOUBLE:"                                                  >> outc
   print tab tab tab "return "fdef"_dbl("cargs_dbl");"                                      >> outc
   print tab tab "case HYPRE_REAL_LONGDOUBLE:"                                              >> outc
   print tab tab tab "return "fdef"_long_dbl("cargs_ldb");"                                 >> outc
   print tab tab "default:" >> outc
   if(fret == "void")
   {
      print tab tab tab "hypre_error_w_msg(HYPRE_ERROR_GENERIC, \"Unknown solver precision\");" >> outc
   }
   else
   {
      print tab tab tab "{ "fret" value = 0; hypre_error_w_msg(HYPRE_ERROR_GENERIC, \"Unknown solver precision\"); return value; }" >> outc
   }
   print tab "}"                                                                            >> outc
   print "}\n"                                                                              >> outc
}

#################################################################################
#
# Parse a line of the prototype info file and create global strings 'pargs' and
# 'cargs' containing arguments for a function prototype and a function call,
# respectively.  Global variable 'nargs' is also created.
#
#################################################################################

function ParseArgs(          i, argall, argvar, laststart, lastlength)
{
   pargs = ""
   cargs = ""
   nargs = NF-2

   for(i = 3; i <= NF; i++)
   {
      argall = $i
      # Find the last legal C token in the argument (should not start with numbers)
      # This helps to address arguments like const double foo[3]
      laststart  = 1;
      lastlength = 0;
      while ( match(substr($i, laststart + lastlength), /[a-zA-Z_][a-zA-Z0-9_]*/) )
      {
         laststart  = laststart + lastlength + RSTART - 1
         lastlength = RLENGTH
      }
      argvar = substr($i, laststart, lastlength)
      # Update the argument strings
      if(i < NF)
      {
         pargs = pargs argall ","
         cargs = cargs argvar ","
      }
      else
      {
         pargs = pargs argall
         cargs = cargs argvar
      }
   }
   if (nargs == 0)
   {
      # Special case Foo(void) function
      pargs = "void"
      cargs = ""
   }
}
