/*******************************************************************************
* YORKTOWN ADVANCED SGML PARSER (YASP)                                         *
*         An SGML Parser conforming to International Standard ISO 8879 --      *
*         Standard Generalized Markup Language                                 *
*------------------------------------------------------------------------------*
* Written 1987 - 1993 by                                                       *
*         Pierre G. Richard                                                    *
*         IBM T.J. Watson Research Center                                      *
*         Yorktown Heights, New York                                           *
*         petrus@watson.ibm.com                                                *
*------------------------------------------------------------------------------*
* FUNCTION NAME    = YASP / SGML Library                                       *
* STATUS           = Version 0, Release 1, Level 34 and up                     *
* COMPONENT NAME   = errorh.c                                                  *
* DESCRIPTIVE NAME = Error manager                                             *
* DATE_WR          = 04/07/1987                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
*                                                                              *
* Format and print an error.  Returns or exit (depending on severity).         *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits  Change & reason (brief)                               *
*      1 01/30/90 PETRUS Simplified - fmmgr merged                             *
*      2 02/15/90 PETRUS Enhanced message service function (PAC005)            *
*******************************************************************************/

/*-------------+
|Include Files |
+-------------*/
#include "machine.h"
#include "portable.h"
#include "yaspapi.h"
#include "global.h"
#include "usrlib.h"
#include "sgml.h"
#include "gtype.h"
#include "message.h"

/*---------------+
|Local Functions |
+---------------*/
static void  LP_FCT send_msg (GA_P, MSGCODE, uint, ADLEN *);
static uint  LP_FCT stoi     (uchar const *, uint *);
static uint  LP_FCT ivformat (uchar STACK_BASED *, ADLEN *, MSGCODE, va_list);
static boolean _loadds LP_FCT is_displayable (uint);
static uint    _loadds LP_FCT ulbtos         (ulong,uchar *, int);
#if (defined(IBMCAIX) || defined(IBMC370) || defined(IBMCSET2))
/* ANSI construct that is not recognized by these compilers... */
static uchar const *       _loadds LP_FCT get_message(MSGCODE i);
#else
static uchar const * const _loadds LP_FCT get_message(MSGCODE i);
#endif

#if (defined(OS2V1_SYS) || defined(OS2V2_SYS) || defined(DOS_SYS))
#define ARROW_SIGN ''
#elif (defined (CMS_SYS) || defined(MVS_SYS))
#define ARROW_SIGN ':'
#else
#define ARROW_SIGN '^'
#endif

/*STATIC--------------------------------------------------------------send_msg-+
| Send a message to the system                                                 |
| The pac_p->errcod, pac_p->errsev, pac_p->msglast must have been set.         |
+-----------------------------------------------------------------------------*/
static void LP_FCT send_msg(
   GA_P     gbla_p,
   MSGCODE  msgcod,
   uint     adlen_count,
   ADLEN   *adlen_p)
{
   PAC *pac_p = gbla_p->pac_p;
   int rc;

   pac_p->dad.len = adlen_count;
   pac_p->dad._.p = (void *)adlen_p;
   pac_p->msgcod = msgcod;
   pac_p->func = MSG_PFC;         /* "write message" service */
   if ((rc=pac_p->ase(pac_p))!=0) exit(rc);
}

/*STATIC----------------------------------------------------------------ulbtos-+
| Unsigned Long Based TO String.                                               |
| Convert a long into a non-zero terminated string, given a radix.             |
| The target string must be enough large to handle the result:                 |
| 32 bits int: 32 bytes.                                                       |
+-----------------------------------------------------------------------------*/
static uint _loadds LP_FCT ulbtos(
   ulong val,             /* number to convert */
   uchar *c_p,            /* address of the string (33 bytes) */
   int radix              /* conversion radix */
) {
   static   uchar const digit[]= "0123456789ABCDEF";
   auto     uint j = 0;
   auto     uchar *d_p, d;

   d_p = c_p;
   do {                   /* convert */
      *d_p++ = digit[(uint)(val%radix)];
      ++j;
   }while ((val /= radix) != 0);
#if (defined(IBMCSET2) && (__IBMC__ == 200))  /* IBM C/C++ 2.0 optimizer bug */
   d_p = c_p + j;         /* this instruction has no real effect */
#endif
   while (--d_p > c_p) {  /* reverse the string */
      d = *c_p;
      *c_p++= *d_p;
      *d_p = d;
   }
   return(j);
}

/*STATIC------------------------------------------------------------------stoi-+
| String TO Integer conversion.                                                |
| The string is read until a non-numeric character is found,                   |
| or an overflow occurs.  The number of string characters read is returned.    |
| The codeset of the machine is assumed to group all digits in adjacent        |
| bit combinations.                                                            |
+-----------------------------------------------------------------------------*/
static uint LP_FCT stoi(uchar const *c_p, uint *i_p)
{
   register uint i = 0;
   register uint k;
   uint j;

   for (j=0, k = *c_p;
      ( (k >= '0') && (k <= '9') )      /* cannot use is_digit */
      && ( (i < ((uint)~0)/10)||((i==((uint)~0)/10)&&(k<='5')) );
      k = *(++c_p), ++j) {
      i = ((i +(i<<2))<<1) + k - '0';    /* i*10 + c - '0' */
   }
   *i_p = i;
   return(j);
}

/*STATIC-----------------------------------------------------------get_message-+
|                                                                              |
+-----------------------------------------------------------------------------*/
#if (defined(IBMCAIX) || defined(IBMC370) || defined(IBMCSET2))
static uchar const *       _loadds LP_FCT get_message(MSGCODE i)
#else
static uchar const * const _loadds LP_FCT get_message(MSGCODE i)
#endif
{
   return(message_array[i]);
}

/*STATIC--------------------------------------------------------------ivformat-+
|Format variables under control of a printf_type format_string                 |
|Generates a table of ADLENs for each decoded value.                           |
|Returns the number of decoded values.                                         |
|                                                                              |
|IMPLEMENTATION NOTES:                                                         |
|                                                                              |
|The float conversion is *NOT* implemented.                                    |
+-----------------------------------------------------------------------------*/
static uint LP_FCT ivformat(
   uchar STACK_BASED *t_p,
   ADLEN *adlen_p0,
   MSGCODE msgcode,
   va_list arg_p)                  /* ANSI style (portable) */
{
   uchar STACK_BASED *t_p0;
   ADLEN *adlen_p;
   uchar const * s_p0;
   uchar const * s_p;
   uchar buffer[8*sizeof(long)];
   register uchar *c_p;
   int radix;
   union {
      struct {
         uint ljustify   : 1;
         uint precision  : 1;
         uint longout    : 1;
         uint showsign   : 1;
      }b;
      int i;
   }flag;
   uint width, precision, length;
   long il;
   int  i, j;
   uchar pad;

   adlen_p = adlen_p0;
   s_p = get_message(msgcode) - 1;         /* dummy entry */
   while (*++s_p) {
      if (*(s_p0 = s_p) != '%') break;
      flag.i = 0;                          /* reset all flags to zero */
      if (*++s_p=='-') {
         flag.b.ljustify = 1;
         ++s_p;
      }
      if (*s_p == '0') {
         pad = '0';
         ++s_p;
      }else {
         pad = ' ';
      }
      s_p += stoi(s_p,&width);
      if (*s_p == '.') {
         flag.b.precision = 1;
         s_p += stoi(++s_p,&precision);
      }
      if (*s_p == 'l') {
         flag.b.longout = 1;
         ++s_p;
      }
      switch(*s_p) {
      case 's':
         radix = 0;
         length = strlen(c_p = va_arg(arg_p, uchar *));
         if (flag.b.precision && precision<length) length=precision;
         break;
      case 'c':
         radix = 0;
         c_p = buffer;
         *c_p = (uchar)va_arg(arg_p,uint); /* Note: uint instead of uchar ! */
         length = 1;
         break;
      case 'b':
         radix = 2;
         break;
      case 'o':
         radix = 8;
         break;
      case 'u':
         radix = 10;
         break;
      case 'x':
      case 'X':
         radix = 16;
         break;
      case 'd':
         flag.b.showsign = 1;
         radix = 10;
         break;
      default:                       /* just in case... */
         memmove(adlen_p->_.p=t_p, s_p0, length = s_p - s_p0 + 1);
         t_p += ((adlen_p++)->len = length);
         *(t_p++) = '\0';
         continue;
      }
      if (radix) {
         c_p = buffer;
         j = 0;
         if (flag.b.longout) {
            if (((il = va_arg(arg_p, long)) < 0) && flag.b.showsign) {
               il = -il;
               *c_p = '-';
               j = 1;
            }
         }else {
            if (((i = va_arg(arg_p,int)) < 0) && flag.b.showsign) {
               i = -i;
               *c_p = '-';
               j = 1;
            }
            il = (uint)i;
         }
         length = j + ulbtos((ulong)il,c_p+j,radix);
      }
      adlen_p->_.p = t_p0 = t_p;
      if (!flag.b.ljustify) {
         while (width > length) {
            *(t_p++) = pad;
            --width;
         }
      }
      memmove(t_p,c_p,length);
      t_p += length;
      while (width > length) {
         *(t_p++) = pad;
         --width;
      }
      (adlen_p++)->len = t_p - t_p0;
      *(t_p++) = '\0';
   }
   return(adlen_p - adlen_p0);
}

/*----------------------------------------------------------------------errorh-+
| Error handler                                                                |
|                                                                              |
| action_flags = ERH_x_SEV + ERH_LOCATE?                                       |
|                                                                              |
| Note that the location of the error (arrow) works even if the error appears  |
| outside a record-oriented context.                                           |
+-----------------------------------------------------------------------------*/
void C_FCT errorh(
   GA_P gbla_p,
   MSGCODE msgcod,
   uint action_flags,
   ...)
{
   int i;                             /* keep it before message */
   uchar message[MSG_MAXSIZ + 1];
   ADLEN adlen[9];                    /* no more than 9 parameters allowed */
   uchar STACK_BASED *c_p;
   uint j, k;
   va_list arg_p;
   int row, col;
   uchar *name_p, *appl_info_p;
   MSGCODE n_msgcod;

   if (!(action_flags & ERH_COMPLT)) {    /* for the main message */
      ((PAC *)(gbla_p->pac_p))->errcod = msgcod;
      ((PAC *)(gbla_p->pac_p))->errsev = action_flags & ERH_SEV_MASK;
      ((PAC *)(gbla_p->pac_p))->msglast = 0;
   }
   if ((action_flags & ERH_LOCATE) && GA(enty_level)) {
      i = 1;
      do {
         em_locat(gbla_p, i, &name_p, &appl_info_p, &row, &col);
         if (i==1) {
            errorh(
               gbla_p,ER_LOC0,ERH_I_SEV | ERH_COMPLT,
               appl_info_p, row, col, em_ofs(gbla_p)+1
            );
         }else if (name_p) {                     /* entity %s */
            if (appl_info_p) {
               errorh(
                  gbla_p,ER_LOC1,ERH_I_SEV | ERH_COMPLT,
                  name_p, appl_info_p, row, col
               );
            }else {
               errorh(gbla_p,ER_LOC2,ERH_I_SEV | ERH_COMPLT, name_p, col);
            }
         }else {
            errorh(gbla_p,ER_LOC3,ERH_I_SEV | ERH_COMPLT,col);
         }
      }while (++i <= GA(enty_level));

      j = 0;
      do {
         em_unrd(gbla_p,1);
         if (++j >= MSG_MAXSIZ - 1) break;
      }while (GA(curr_char) != GA(RS_char));
      i = j;
      c_p = message;
      k = 0;
      for (; ;) {
         em_read(gbla_p);
         if (--i <= 0) {
            if (action_flags & ERH_NONSGML) break;  /* don't read after */
            if ((GA(curr_char) == GA(RE_char))||(GA(curr_char) == Ee)) break;
         }
         /*---------------------------------------+
         |Try to give a readable image of the line|
         +---------------------------------------*/
         if (is_displayable(GA(curr_char))) {
            *c_p++ = (uchar)GA(curr_char);    /* that's OK */
         }else if ((GA(curr_char) == '\n')||(GA(curr_char) == '\r')) {
            if (i > 0) --j;
            continue;
         }else {
            *c_p++ = ' ';                     /* non-graphic, HT, VT, FF */
         }
         if (++k >= MSG_MAXSIZ - 1) break;
      }
      message[k] = '\0';                      /* k is the length */
      adlen[0]._.p = message;
      adlen[0].len = k;
      send_msg(gbla_p,ER_LINE,1,adlen);
      memset(message,' ',j-1);
      message[j-1] = ARROW_SIGN;
      message[j] = '\0';                      /* j is the length */
      adlen[0].len = j;
      send_msg(gbla_p,ER_INDIC,1,adlen);
      em_unrd(gbla_p,-i);
   }
   if (msgcod >= UNKNOWN) {           /* in case unknown errcod (tests only) */
      n_msgcod = UNKNOWN;
      va_start(arg_p,gbla_p);         /* so next arg is "msgcod" */
   }else {
      n_msgcod = msgcod;
      va_start(arg_p,action_flags);   /* so next arg is "..." */
   }
   i = ivformat((uchar STACK_BASED *)message,adlen,n_msgcod,arg_p);
   va_end(arg_p);
   if (!(action_flags & ERH_COMPLT)) {
      ((PAC *)(gbla_p->pac_p))->msglast = 1;
   }
   send_msg(gbla_p,msgcod,i,adlen);
}

/*STATIC--------------------------------------------------------is_displayable-+
| Check if a character can be displayed.                                       |
| We need to use _loadds in OS2, because ds belongs to the caller, so          |
| the gtype table (used by isgraph) would not be properly addressed.           |
+-----------------------------------------------------------------------------*/
#include <ctype.h>
static boolean _loadds LP_FCT is_displayable(uint c)
{
#if defined(IBMCSET2)
#undef islower
#undef isupper
#undef isdigit
   if (c >= ' ') {
      return(TRUE);
   }else {
      return(FALSE);
   }
#else
   return(isgraph(c) ? TRUE : FALSE);
#endif
}
/******************************************************************************/
