/*******************************************************************************
* 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 / CSD compiler                                       *
* STATUS           = Version 0, Release 1, Level 33 and up                     *
* COMPONENT NAME   = ocsdlim.c                                                 *
* DESCRIPTIVE NAME = Decode and build the tables for the Delimiter Set         *
* DATE_WR          = 03/28/1987                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
*                                                                              *
* Following tables are handled by this module:                                 *
*                                                                              *
*  dstab: A fixed_size table that contains offset in the string buffer         *
*         to a finite number of delimiters.                                    *
*  dltab: A "could-be-a-delimiter" table used to recognize quickly             *
*         if a character could start a delimiter string                        *
*         (recognition mode dependent)                                         *
*  dvtab: A special tree structure to quickly decode delimiters (using         *
*         dl_find)                                                             *
*                                                                              *
*  srtab: The SHORTREF delimiter table, used to recognize                      *
*         the parameter literal of the short_ref_mapping declaration.          *
*                                                                              *
*  gtype: A map of the character set: all delimiter character are significant, *
*         therefore Ored with the SGML class (in case they were shunned).      *
*                                                                              *
* IMPLEMENTATION NOTE =                                                        *
* The code of this module is not an easy one.  The way we build the 3 tables   *
* allows a fast recognition of the delimiter, that is the main condition       *
* for the speed of the parser.  See dl_find code for more information on the   *
* use of the dvtab (also the document entitled: "A fast SGML delimiter         *
* recognition algorithm").                                                     *
*                                                                              *
* DEPENDENCIES =  (see Include list)                                           *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits    Change & Reason (brief)                             *
*      1 03/29/88 PETRUS   Applied Technical Errata of April 30, 1987          *
*      2 04/17/88 PETRUS   Introduced "srtab" for the <!SHORTREF parm rec.     *
*      3 09/30/88 PETRUS   Enhanced version (use library tools)                *
*      4 02/17/89 PETRUS   Use a COMMON shared with OCSCHAR rather than args   *
*                          passing.  Significant SGML flagged in gtype.        *
*      5 10/20/89 PETRUS   Fix trapping duplicate delimiters (DUPDLM)          *
*      6 03/03/91 PETRUS   B indicates a B-Seq only if SR delim                *
*      7 03/07/91 PETRUS   Check if user delim as made solely of funchar       *
*      8 03/09/91 PETRUS   Clean up (after 6 and 7)                            *
*******************************************************************************/

/*-------------+
|Include Files |
+-------------*/
#include "machine.h"
#include "portable.h"
#include "yaspapi.h"
#include "global.h"
#include "usrlib.h"
#include "sgml.h"
#include "gtype.h"      /* For BSEQ in new_gtype */
#include "ocs.h"

/*-------------------+
|Local data and types|
+-------------------*/
typedef struct {
   uchar *word;
   TK token;
}KYWD;

typedef struct t_TREE {      /* Threaded tree, as usual... */
   uint ltag    :1;
   uint rtag    :1;
   uint is_bseq :1;          /* indicates a bseq, rather than a prep char */
   /**/                      /* at the primary AND secondary levels */
   struct t_TREE *llink;
   struct t_TREE *rlink;
   int len;
   union {
      uint bseqlen;          /* B-SEQ minimum length (Prim Node) */
      uchar pc;              /* Preponderant Character of a 2nd Node */
   }u;
   uchar *info;
   TK   token;
}TREE;

typedef struct {
   COMMON_P M_p;
   KYWD *kywd_p;
   int tot_dv;
   uint prev_bseq;
   DV   *dv_orig, *dv_p;
   uchar *pc_p;              /* predominant character list */
}LOCAL;

/*---------------+
|Local Functions |
+---------------*/
static uchar const * _loadds LP_FCT adr_sr_str_lit(ETY *);
static void    LP_FCT get_sr_str    (COMMON_P, uchar *);
static int     P_FCT  delim_compare (uchar *, uchar *, GA_P);
static void    LP_FCT install_delim (COMMON_P, KYWD *, uchar *, ALLMO);
static void    LP_FCT install_ddlmv (COMMON_P, TK, uchar *);
static void    LP_FCT check_dup_dlm (LOCAL STACK_BASED *, uint, uint, int);
static void    LP_FCT get_stub(
   uint, uint, uint, uint, TREE *, uchar *, LOCAL STACK_BASED *
);
static void    LP_FCT prim_postorder(TREE *, LOCAL STACK_BASED *);
static void    LP_FCT secd_postorder(TREE *, LOCAL STACK_BASED *);

/*--------------------------------------------------------------+
| These macros allow us to find if a character will belong to   |
|  a blank sequence, a namestart, or a CREF constraint          |
| relatively to the new syntax character set.                   |
|  For a blank sequence:                                        |
| - fncrtab is already pointing the new fncrtab (see ODTCOMP)   |
|   and fncrtab[2] is always the space character entry          |
| - M_p->gtype is used instead of GA(type),                     |
|   as GA(type) still reflects the Syntax Refrence character set|
+--------------------------------------------------------------*/
#define WILL_BE_BLANK(c) \
   ((c==(uchar)(GA(fncrtab)[2].ch_nb))||((M_p->gtype)[c] & (S_SEPCHAR)))
#define WILL_BE_NMS(c)   ((M_p->gtype)[c] & (S_LETTER | S_NMSTRT))
#define WILL_BE_CREF(c)  ((M_p->gtype)[c] & (S_LETTER | S_NMSTRT | S_DIGIT))

/*STATIC--------------------------------------------------------adr_sr_str_lit-+
| Allow temporary access to local constant area (shortref string)              |
+-----------------------------------------------------------------------------*/
static uchar const * _loadds LP_FCT adr_sr_str_lit(ETY *ety_p)
{
  static uchar const sr_str_lit[] =
  "' &#TAB; &#RE; &#RS; &#RS;B &#RS;&#RE; &#RS;B&#RE; B&#RE; &#SPACE; BB \" # '"
  "\"% ' ( ) * + , - -- : ; = @ [ ] ^ _ { | } ~  \"";

  static uchar const incrt[] = { 29,        /* 1st string size expected */
           2,    2,    2,     3,         3,          4,     3,       2, 3, 2,2,
      2,2,2,2,2,2,2,2, 3,2,2,2,2,2,2,2,2,2,2,2,2,1
  };
  ety_p->val.lit._.p = (uchar *)sr_str_lit;
  ety_p->val.lit.len = sizeof(sr_str_lit) - 1;
  return(incrt);
}

/*STATIC------------------------------------------------------------get_sr_str-+
| Get in str_p the Reference (default) Shortref Strings, separated             |
| with '\0' and ended with a doubled '\0'.                                     |
|                                                                              |
| We cannot have a static translation of the Character Reference,              |
| as these could have been changed by the FUNCTION parameter.                  |
| To translate the sr_str_lit, this string is "unread" in a pseudo entity      |
| and processed by get_lit (that does the chref translation)                   |
| Due to the presence of ' mixed with ", two get_lit are needed.               |
|                                                                              |
| Note that there is no problem to use the name SPACE or RE: these cannot      |
| have been changed so far !  (NAMES is after DELIM ... SHORTREF).             |
+-----------------------------------------------------------------------------*/
static void LP_FCT get_sr_str(COMMON_P M_p, uchar *str_p)
{
   GA_P gbla_p = M_p->gbla_p;
   ETY lit_ety;                            /* to decode sr_str_lit */
   uchar const *incrt_p;                   /* array of increments */
   uchar *c_p;
   uint i;

   lit_ety.is = RCDATA_EF;                 /* tell what it is.. */
   lit_ety.kind = TEXT_EFK;
   lit_ety.name._.p = (uchar *)0;          /* pseudo entity */
   incrt_p = adr_sr_str_lit(&lit_ety);     /* fill it with the sr_str string */
   em_open(gbla_p,&lit_ety);               /* re-read it */
   dl_find(gbla_p);                        /* skip the '"' */
   i = ocs_gplt(M_p,str_p,FALSE);          /* read the lit with the '"' in it */
   /*-----------------------------------------+
   | If &#TAB; was not replaced by h=get_chrf,|
   | the length of the string is off by 1     |
   +-----------------------------------------*/
   if (i != *incrt_p) {
     memmove(str_p, str_p+1, --i);
     ++incrt_p;
   }
   dl_find(gbla_p);                        /* skip the "'" */
   ocs_gplt(M_p,str_p+i,FALSE);            /* catenate lit with the "'" in it */
   em_close(gbla_p);
   *(c_p = str_p) = '\0';                  /* Make each SR an Z-string */
   do *(c_p += *++incrt_p)='\0'; while (*incrt_p != 1); /* double zero at end */
}

/*STATIC---------------------------------------------------------delim_compare-+
| Comparison between two delimiters - used to sort the delim table             |
| The B-sequence (for ShortRef's) will be greater than any other character.    |
| Note: Duplicates are caught in check_dup_dlm, called by get-stub.            |
+-----------------------------------------------------------------------------*/
static int P_FCT delim_compare(uchar *a1_p, uchar *a2_p, GA_P gbla_p)
#define kywd1_p ((KYWD *)a1_p)
#define kywd2_p ((KYWD *)a2_p)
{
   register uchar *c_p1 = kywd1_p->word - 1;
   register uchar *c_p2 = kywd2_p->word - 1;

   while ((*++c_p1 == *++c_p2)&&(*c_p1));          /* until it differs or EOS */
   if ((kywd1_p->token >= SR_first)&&(*c_p1 == gbla_p->BSEQ_char)) {
      return(1);
   }else if ((kywd2_p->token >= SR_first)&&(*c_p2 == gbla_p->BSEQ_char)) {
      return(-1);
   }else {
      return(*c_p1 - *c_p2);
   }
}

/*STATIC---------------------------------------------------------install_delim-+
| Install a new delimiter:                                                     |
| -Update the M_p->gtype to make all its characters belong to the SGML class   |
| -Register the first character into DLTAB (fast recognition)                  |
| -Prefix the delimiter with a,b,c... = f(mode) to force recognition mode      |
|  as a major key (for sort)                                                   |
| -Install the resulting string in the sa_array / delim_id                     |
+-----------------------------------------------------------------------------*/
static void LP_FCT install_delim(
   COMMON_P M_p,
   KYWD    *kywd_p,
   uchar   *delim_p,
   ALLMO    allmode)
{
   GA_P gbla_p = M_p->gbla_p;
   uchar c, *c_p;
   uint  i;
   boolean is_SR = (kywd_p->token >= SR_first) ? TRUE : FALSE;

   for (c_p = delim_p; *c_p; ++c_p) {              /* force SGML class */
      if ((!is_SR)||(*c_p != gbla_p->BSEQ_char)) { /* Forget 'B' for B-Seq */
         M_p->gtype[*c_p] |= S_SGML;
      }
   }
   if ((is_SR)&&(*delim_p == gbla_p->BSEQ_char)) { /* B is for BSEQs.. */
      for (i=0; i < MAX_CHARSET; ++i) {
         if ((i==GA(SPACE_char))||(M_p->gtype[i] & S_SEPCHAR)) {
            *(M_p->dltab + i) |= allmode;
         }
      }
   }else {
#if (defined(IBMCSET2))                    /* IBM C/Set2 bug (01/04/92, V1.0) */
      *(M_p->dltab + (uint)(*delim_p)) |= allmode;
#else
      *(M_p->dltab + *delim_p) |= allmode; /* mode in dltab[first char] */
#endif
   }
   i = 1 + strlen(delim_p);
   for (c='a'; allmode; allmode >>= 1, ++c) {
      if (allmode & 1) {             /* could be not executed at all */
         *(kywd_p->word = (uchar *)malloc(1+i)) = c;
         memcpy(kywd_p->word+1,delim_p,i);
         sa_instl(M_p->delim_id,(uchar *)kywd_p);
      }
   }
}

/*STATIC-------------------------------------------------------install_ddlmv-+
|Solve the string forming the combined delimiters                            |
+---------------------------------------------------------------------------*/
static void LP_FCT install_ddlmv(COMMON_P M_p, TK token, uchar *c_p)
{
   GA_P   gbla_p = M_p->gbla_p;
   DDLMV  const *ddlmv_p;
   struct t_DDLMV_STR *ddlmv_str = M_p->ddlmv_str;
   uint   i, j;

   j = 1 + strlen(c_p);
   for (ddlmv_p=GA(ddlmv_tab), i=0; i < NBR_OF_DDLMV; ++i, ++ddlmv_p) {
      if (token == ddlmv_p->tk1) {
         memcpy(ddlmv_str[i].s1 = (uchar *)malloc(j),c_p,j);
      }
      if (token == ddlmv_p->tk2) {
         memcpy(ddlmv_str[i].s2 = (uchar *)malloc(j),c_p,j);
      }
   }
}

/*---------------------------------------------------------------------dlm_bld-+
| Decode the DELIM parameter of the SYNTAX of the SGML declaration.            |
|                                                                              |
| [190] delimiter set =                                                        |
|            "DELIM", ps+, general delimiters, short reference delimiters      |
|                                                                              |
| [191] general delimiters =                                                   |
|            "GENERAL", ps+, "SGMLREF", (ps+, name, ps+, parameter literal)*   |
|                                                                              |
| [192] short reference delimiters =                                           |
|            "SHORTREF", ps+, ("SGMLREF"|"NONE), (ps+, parameter literal)*     |
|                                                                              |
| At entry to the main routine (dlm_bld), the next character on input          |
| must be "DELIM".  str_p is pointing a working area >= LITLEN+1 bytes.        |
|                                                                              |
| It returns when all short references have been processed (next character on  |
| input should be "NAMES")                                                     |
|                                                                              |
| dstab (OFS of some delimiters) is created and its address is set in          |
| caller's storage.                                                            |
|                                                                              |
| dltab (256 byte "could_be_delimiter" table) is processed in the same way,    |
| but need to be updated later by dlm_dvtb.                                    |
|                                                                              |
| srtab: The SHORTREF delimiter table, used to recognize                       |
|        the parameter literal of the short_ref_mapping declaration.           |
|                                                                              |
| Call "samgr" to sa_instl the delimiter (this precludes to the creation       |
| of dvtab by dlm_dvtb).  The "id" of the "sa process" is kept into            |
| M_p->delim_id to be used later by "dlm_dvtb".                                |
|                                                                              |
+----------------------------------------------------------------------------*/
void P_FCT dlm_bld(COMMON_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   void  *delim_id;
   uchar *c_p;
   uint   i;
   DLMV   const *dlmv_p;
   DLMV   const *dlmv_tab;
   DDLMV  const *ddlmv_p;
   KYWD   kywd;
   EQTK   srwd;
   void  *sr_id;
   DSTAB_ITM *dstab_itm_p;
   boolean is_installed_dlm[NBR_OF_DLMV];

   dlmv_tab = GA(dlmv_tab);
   memset((uchar *)is_installed_dlm,FALSE,sizeof(is_installed_dlm));

   chk_gnam(gbla_p,GA(alwdtab_p)->DELIM.s.p);
   skip_Wps(gbla_p);
   chk_gnam(gbla_p,GA(alwdtab_p)->GENERAL.s.p);
   skip_Wps(gbla_p);
   chk_gnam(gbla_p,GA(alwdtab_p)->SGMLREF.s.p);

   M_p->delim_id = delim_id = sa_init(gbla_p,sizeof(KYWD),delim_compare);
   kywd.token = NOT_FOUND_TK;
   *(kywd.word = (uchar *)malloc(1))='\0';
   sa_instl(delim_id,(uchar *)&kywd);

   /*----------------------------------+
   |Decode and Install User Delimiters |
   +----------------------------------*/
   c_p = GA(lit_p);
   for (; ;) {
      int mid, cond, num, low;

      skip_Wps(gbla_p);
      get_name(gbla_p,c_p);         /* c_p is GA(lit_p) */

      /* Binary search for "mid", index of the dlm in dlmv_tab */
      cond = 1;
      num = NBR_OF_DLMV - 1;
      low = 0;                      /* low bound */
      while (low <= num) {
         mid=((uint)low+(uint)num)/2;
         cond = strcmp(c_p,(dlmv_tab + mid)->named);
         if (cond < 0) {
            num = mid - 1;          /* set low range */
         }else if (cond > 0) {
            low = mid + 1;
         }else {                    /* found */
            break;
         }
      }
      if (cond) errorh(gbla_p,INVROL,ERH_T_SEV+ERH_LOCATE);
      dlmv_p = dlmv_tab + mid;
      if ((kywd.token = dlmv_p->token) == SHORTREF_TK) {
         is_installed_dlm[mid] = TRUE;   /* SHORTREF is just a generic name */
         break;                          /* and breaks the loop */
      }
      skip_ps(gbla_p);
      i = ocs_gplt(M_p,c_p,TRUE);   /* Check if made of solely function chars */
      if (!i) {
         if (*c_p) {
            errorh(gbla_p,INVDLSTR1,ERH_W_SEV+ERH_LOCATE);
         }else {
            errorh(gbla_p,INVDLSTR0,ERH_W_SEV+ERH_LOCATE);
         }
      }else if (is_installed_dlm[mid]) {
         errorh(gbla_p,DUPNAMDLM,ERH_W_SEV+ERH_LOCATE);
      }else {
         switch (kywd.token) {
            /* Later, if SCOPE INSTANCE, check the delimiter change validity */
         case MDO_TK:
            if (strcmp(dlmv_p->assigned, c_p)) M_p->is_dlm_change.mdo = TRUE;
            break;
         case COM_TK:
            if (strcmp(dlmv_p->assigned, c_p)) M_p->is_dlm_change.com = TRUE;
            break;
         case PIO_TK:
            if (strcmp(dlmv_p->assigned, c_p)) M_p->is_dlm_change.pio = TRUE;
            break;
         case MDC_TK:
            if (strcmp(dlmv_p->assigned, c_p)) M_p->is_dlm_change.mdc = TRUE;
            break;
         default:
            break;
         }
         is_installed_dlm[mid] = TRUE;
         if (dlmv_p->dstab_ix != -1) {   /* selected single delims in dstab */
            (M_p->dstab)[dlmv_p->dstab_ix].ofs=sk_park(gbla_p,c_p,1+i);
         }
         install_ddlmv(M_p, kywd.token, c_p);
         install_delim(M_p,&kywd,c_p,dlmv_p->allmode);
      }
   }
   /*-----------------------------------------+
   |Default uninstalled delimiters to SGMLREF |
   +-----------------------------------------*/
   sr_id = sa_init(
       gbla_p, sizeof(EQTK), (int (P_FCT *)(uchar *, uchar *, GA_P))0
   );
   for (dlmv_p = dlmv_tab, i=0; i < NBR_OF_DLMV; ++dlmv_p) {
      if (!is_installed_dlm[i++]) {          /* if not already installed */
         uchar *d_p = c_p - 1;
         uchar *e_p = dlmv_p->assigned -1;   /* default to reference */
         int chnb;

         while (*++e_p) {
            if ((chnb = ocs_chrf(M_p,*e_p)) != -1) *(++d_p) = (uchar)chnb;
         }
         *++d_p = '\0';
         if (dlmv_p->dstab_ix != -1) {   /* selected single delims in dstab */
            (M_p->dstab)[dlmv_p->dstab_ix].ofs = sk_park(gbla_p,c_p,1+d_p-c_p);
         }
         kywd.token=dlmv_p->token;
         install_ddlmv(M_p, kywd.token, c_p);
         install_delim(M_p,&kywd,c_p,dlmv_p->allmode);
      }
   }
   /*---------------------------------------------+
   |Install selected combined delimiters in dstab |
   +---------------------------------------------*/
   for (ddlmv_p=GA(ddlmv_tab), i=0; i < NBR_OF_DDLMV; ++i, ++ddlmv_p) {
      switch(ddlmv_p->token) {
      default:
         continue;
      case MDO_DSO_TK:
         dstab_itm_p= M_p->dstab + MDO_DSO_ix;
         break;
      case MSC_MDC_TK:
         dstab_itm_p= M_p->dstab + MSC_MDC_ix;
         break;
      }
      {
         uchar *c1_p = M_p->ddlmv_str[i].s1;
         uchar *c2_p = M_p->ddlmv_str[i].s2;
         uint s1_len;

         memcpy(c_p,c1_p,s1_len=strlen(c1_p));
         memcpy(c_p+s1_len,c2_p,1+strlen(c2_p));
      }
      dstab_itm_p->ofs = sk_park(gbla_p,c_p,1+strlen(c_p)); /*dstab*/
   }
   /*------------------------------------------+
   |If SHORTREF SGMLREF, install the shortrefs |
   |from the reference concrete syntax         |
   +------------------------------------------*/
   skip_Wps(gbla_p);
   kywd.token = (TK)((int)SR_first - 1);
   if (get_gnam(gbla_p,GA(alwdtab_p)->SGMLREF.s.p)) {
      get_sr_str(M_p,c_p);            /* string of shortrefs in c_p */
      while (*(c_p += 1 + strlen(c_p))) {
         kywd.token = (int)kywd.token + 1;
         srwd.token = kywd.token;
         srwd.s.ofs = sk_park(gbla_p,c_p,1+strlen(c_p));  /* in srtab */
         sa_instl(sr_id,(uchar *)&srwd);
         install_delim(M_p,&kywd,c_p,CON_MO);
      }
      c_p = GA(lit_p);                /* restore c_p pointing our lit */
   }else if (!get_gnam(gbla_p,GA(alwdtab_p)->NONE.s.p)) {
      errorh(gbla_p,SGMLNONE,ERH_T_SEV+ERH_LOCATE);
   }
   /*---------------------------------+
   |Decode and Install User Shortrefs |
   +---------------------------------*/
   while (skip_ps(gbla_p), GA(curr_token) != NOT_FOUND_TK) {
      if (!(ocs_gplt(M_p,c_p,FALSE))) {
         errorh(gbla_p,INVDLSTR0,ERH_W_SEV+ERH_LOCATE);
         continue;
      }
      /*-------------------------------+
      |Check rule at 13.4.6.2          |
      |No more than one BSEQ, cannot be|
      |followed/preceeded by SEPCHAR   |
      +-------------------------------*/
      {
         uchar *d_p = c_p;
         boolean BSEQ_found = FALSE;
         MSGCODE msg_code = ER_NULL;
         while (*d_p) {
            if (*d_p != gbla_p->BSEQ_char) {
               ++d_p;
            }else {
               if (BSEQ_found) {
                  msg_code = INVSRBS1;
                  break;
               }else if ((d_p > c_p) && (WILL_BE_BLANK(*(d_p-1)))) {
                  msg_code = INVSRBS2;
                  break;
               }else {
                  BSEQ_found = TRUE;
                  while (*++d_p ==  gbla_p->BSEQ_char);
                  if (WILL_BE_BLANK(*d_p)) {
                     msg_code = INVSRBS2;
                     break;
                  }
               }
            }
         }
         if (msg_code != ER_NULL) {
            errorh(gbla_p,msg_code,ERH_W_SEV+ERH_LOCATE);
            continue;
         }
      }
      kywd.token = (int)kywd.token + 1;
      srwd.token = kywd.token;
      srwd.s.ofs = sk_park(gbla_p,c_p,1+strlen(c_p));  /* in srtab */
      sa_instl(sr_id,(uchar *)&srwd);
      install_delim(M_p,&kywd,c_p,CON_MO);
   }

   if (M_p->must_abort) errorh(gbla_p,ABORTED,ERH_T_SEV);
   /*----------------------------------+
   |Create the srtab, return to caller |
   +----------------------------------*/
   srwd.token = NOT_FOUND_TK;
   srwd.s.ofs = (OFS)0;                /* put a peg at the end */
   sa_instl(sr_id,(uchar *)&srwd);
   M_p->srtab = ((EQTK *)sa_term(sr_id,&(M_p->sr_max)));
}

/*STATIC---------------------------------------------------------check_dup_dlm-+
| Check if two delimiter or delimiter-in-context are differents.               |
| NOTE: If its a delimiter and delimiter_in-context, we could pursue,          |
| since the latter would prevail.                                              |
+-----------------------------------------------------------------------------*/
static void LP_FCT check_dup_dlm(
   LOCAL STACK_BASED *L_p, uint row_min, uint row_hi, int col)
{
   COMMON_P M_p = L_p->M_p;
   TK token = L_p->kywd_p[row_min].token;
   uchar c;
   uchar previous_c = 0;
   uint row = row_min;

   while (++row < row_hi) {
      c = L_p->kywd_p[row].word[col];
      if (!c) {                        /* delimiter is simply duplicated */
         errorh(M_p->gbla_p, DUPDLM, ERH_T_SEV, L_p->kywd_p[row_min].word+1);
      }else {
         switch (token) {
         default:
            return;                    /* no need to pursue */
         case CRO_TK:
            if (!WILL_BE_CREF(c)) continue;
            break;
         case STAGO_TK:
         case ETAGO_TK:
         case ERO_TK:
         case PERO_TK:
         case MDO_TK:
            if (!WILL_BE_NMS(c)) continue;
            break;
         }
         if (previous_c != c) {        /* avoid to tell it more than once */
            previous_c = c;
            errorh(                    /* Duplicate delimiter-in-context */
               M_p->gbla_p, DUPDIC, ERH_W_SEV,    /* W: YASP can recover */
               L_p->kywd_p[row_min].word+1, L_p->kywd_p[row].word+1
            );
         }
      }
   }
}

/*STATIC--------------------------------------------------------------get_stub-+
| Process a sublist of keywords, and build the tree.                           |
| ENTRY: Sublist coordinates, parent node, prep char, common area              |
| when RETURN: the branch at parent node is updated                            |
+-----------------------------------------------------------------------------*/
static void LP_FCT get_stub(
   uint col_min,
   uint row_min,
   uint row_hi,
   uint row_max,
   TREE *TR_p,
   uchar *c_p,
   LOCAL STACK_BASED *L_p)
{
   GA_P gbla_p = L_p->M_p->gbla_p;
   uint row;
   int col;
   uchar c, *d_p;
   int count;
   boolean in_BSEQ, is_SR;
   boolean BSEQ_found = FALSE;
   TREE *TR_pS;

   /*-----------------------------------+
   |Create a new child node in the tree |
   +-----------------------------------*/
   TR_pS = (TREE *)malloc(sizeof(TREE));    /* new child */
   TR_pS->rlink = TR_p;                     /* set father thread */
   TR_pS->llink = TR_p->llink;              /* inherits brother/uncle thread */
   TR_pS->rtag = TR_pS->ltag = FALSE;       /* just threads */
   TR_p->ltag = TRUE;                       /* Brand new dad */
   TR_p->llink = TR_pS;                     /* link father to his child */
   TR_p = TR_pS;                            /* activate child */
   /*------------------------------------------+
   |If not a dead leaf, get a new brother node |
   +------------------------------------------*/
   if (*c_p) {
      TR_p->token = NOT_FOUND_TK;           /* no info into the primary node */
      TR_pS = (TREE *)malloc(sizeof(TREE)); /* brother */
      TR_pS->llink = TR_p;                  /* set elder thread */
      TR_pS->rlink = TR_p->rlink;           /* inherits father thread */
      TR_pS->rtag = TR_pS->ltag = FALSE;    /* just threads */
      TR_p->rtag = TRUE;                    /* Brand new elder */
      TR_p->rlink = TR_pS;                  /* link elder to his brother */
      TR_p = TR_pS;                         /* activate brother */
      count = 1;
   }else {
      count = 0;
   }
   /*----------------------------------------------------+
   |Determines all subclasses of [row_min..row_max][col] |
   +----------------------------------------------------*/
   for (;;) {
      is_SR = (L_p->kywd_p[row_min].token >= SR_first) ? TRUE : FALSE;
      in_BSEQ = ((is_SR)&&(*c_p==gbla_p->BSEQ_char)) ? TRUE : FALSE;
      /*-----------------------------------------------------------------+
      |Get the longest prefix in the [row_min..row_hi][col_min] subclass |
      |c_p points at the beginning; d_p will point at the end;           |
      |[row_min..row][col] will define the first sub-subclass (if any)   |
      |[row][col] will be the first element of the co-subclass (if any)  |
      |Force a cleavage (avoid continuation string) if:                  |
      |    MODE (col 1)                                                  |
      |    B sequence: on the first B                                    |
      +-----------------------------------------------------------------*/
      for (col=col_min, d_p=c_p, c=*d_p; *d_p;) {
         ++col;
         if ((c = *++d_p) == '\0') {                 /* delimiter found */
            check_dup_dlm(L_p,row_min,row_hi,col);
            row = row_min+1;
         }else {
            row = row_min;
            while ((++row < row_hi) && (c==L_p->kywd_p[row].word[col]) &&
               ((c != gbla_p->BSEQ_char)||
               (is_SR)||(L_p->kywd_p[row].token < SR_first)));
         }
         if ((row < row_hi) ||                       /* 1st sub-class found */
            ((!in_BSEQ)&&(is_SR)&&(c==gbla_p->BSEQ_char)) /* B-seq cleavage */
            || (col==1)) {                           /* Mode cleavage */
            break;
         }
      }
      /*----------------------------------------------+
      |Install the prefix found in the secondary node |
      +----------------------------------------------*/
      if (d_p > c_p) {                           /* not a primary node: */
         if (in_BSEQ) {
            TR_p->is_bseq = BSEQ_found = TRUE;
            TR_p->u.bseqlen = 1;
            while ((*++c_p==gbla_p->BSEQ_char)&&(c_p < d_p)) ++TR_p->u.bseqlen;
            ++count;                             /* need an extra DV */
         }else {
            TR_p->is_bseq = FALSE;
            TR_p->u.pc = *c_p++;                 /* set preponderant char */
         }
         TR_p->len = d_p - (TR_p->info=c_p);
      }
      /*------------------------------------------------+
      |If a sub-subclass exists, process it recursively |
      |Else: set up the token                           |
      +------------------------------------------------*/
      if ((row_min+1 < row_hi)||(col==1)||((is_SR)&&(c==gbla_p->BSEQ_char))) {
         TR_p->token = NOT_FOUND_TK;
         get_stub(col,row_min,row,row_hi,TR_p,d_p,L_p);
      }else {
         TR_p->token = L_p->kywd_p[row_min].token;
      }
      /*--------------------------------------------------------+
      |Check if we can loop for the next co-subclass at col_min |
      |if so, set the boundaries in [row_min .. row-hi]         |
      +--------------------------------------------------------*/
      if (row_hi >= row_max) break;
      c = *(c_p = L_p->kywd_p[row_min = row_hi].word + col_min);
      while ((++row_hi < row_max) && (c == L_p->kywd_p[row_hi].word[col_min]));
      /*---------------------+
      |Get a new brother node|
      +---------------------*/
      TR_pS = (TREE *)malloc(sizeof(TREE)); /* brother */
      TR_pS->llink = TR_p;                  /* set elder thread */
      TR_pS->rlink = TR_p->rlink;           /* inherits father thread */
      TR_pS->rtag = TR_pS->ltag = FALSE;    /* just threads */
      TR_p->rtag = TRUE;                    /* Brand new elder */
      TR_p->rlink = TR_pS;                  /* link elder to his brother */
      TR_p = TR_pS;                         /* activate brother */
      ++count;
   }
   L_p->tot_dv += 1 + count;
   /*----------------------------------------------------+
   |Set up the Preponderant Characters length in Primary |
   +----------------------------------------------------*/
   TR_p->rlink->llink->len = BSEQ_found ? count - 2 : count;
   TR_p->rlink->llink->is_bseq = BSEQ_found;
}

/*STATIC--------------------------------------------------------prim_postorder-+
| Process first node of a left branch of the tree, during postorder traversal. |
| ENTRY: First node of a mode.                                                 |
| when RETURN: the branch starting at this node has been traversed, and freed. |
+-----------------------------------------------------------------------------*/
static void LP_FCT prim_postorder(
   register TREE   *TR_p,
   register LOCAL STACK_BASED *L_p)
{
   GA_P gbla_p = L_p->M_p->gbla_p;

   L_p->dv_p->i.h.count = (uchar)TR_p->len;
   L_p->dv_p->i.h.is_bseq = (boolean)TR_p->is_bseq;
   L_p->dv_p->t.tk = TR_p->token;
   L_p->pc_p = sk_point(
      gbla_p, L_p->dv_p->s.stix = sk_park(gbla_p,(uchar *)0,TR_p->len)
   );
   ++L_p->dv_p;
   secd_postorder(TR_p->rlink,L_p);
   free(TR_p);
}

/*STATIC--------------------------------------------------------secd_postorder-+
| Node visit, and Postorder traversal of the tree.                             |
| The first left node is visited by prim_postorder (predominant char list).    |
| when RETURN: the branch starting at this node has been traversed, and freed. |
+-----------------------------------------------------------------------------*/
static void LP_FCT secd_postorder(
   register TREE *TR_p,
   register LOCAL STACK_BASED *L_p)
{
   uint *dvix_p;
   GA_P gbla_p = L_p->M_p->gbla_p;

   if (!TR_p->is_bseq) {
      *L_p->pc_p++ = TR_p->u.pc;        /* update predominant char list */
   }
   L_p->dv_p->t.tk = TR_p->token;       /* set the token */
   dvix_p = &L_p->dv_p->i.dvix;         /* keep a pointer to left pk index */
   if (TR_p->len) {
      *(sk_point(gbla_p, L_p->dv_p->s.stix =
         sk_park(gbla_p,TR_p->info,1+TR_p->len)) + TR_p->len) = '\0';
   }else {
      L_p->dv_p->s.stix = (OFS)0;       /* point a null string */
   }
   if (TR_p->is_bseq) {
      (++L_p->dv_p)->t.bseqlen = TR_p->u.bseqlen;
      L_p->dv_p->i.dvix = L_p->prev_bseq;   /* .CCS identicals => .OCS too: */
      L_p->dv_p->s.stix = (OFS)0;           /* erase LSB (not mandatory) */
      if ((!TR_p->len) &&                   /* if there is no continuation */
         (TR_p->ltag) &&                    /* and the primary child node */
         (TR_p->llink->is_bseq)) {          /* has a brother BSEQ */
         L_p->prev_bseq = diffptr(L_p->dv_p, L_p->dv_orig) - 1;
         L_p->dv_p->s.must_recur = TRUE;    /* turn on recurse flag */
      }else {
         L_p->dv_p->s.must_recur = FALSE;   /* turn off recurse flag */
         L_p->prev_bseq = 0;                /* reset prev_bseq */
      }
   }
   ++L_p->dv_p;
   if (TR_p->rtag) secd_postorder(TR_p->rlink,L_p);
   if (TR_p->ltag) {
      *dvix_p = L_p->dv_p - L_p->dv_orig;
      prim_postorder(TR_p->llink,L_p);
   }else {
      *dvix_p = 0;                      /* leaf: not forward referenced */
   }
   free(TR_p);                          /* free this node */
}

/*-------------------------------------------------------------------dlm_dvtb-+
|  Main Routine.                                                              |
|                                                                             |
|  Update dltab                                                               |
|                                                                             |
|  dltab: table to recognize if a character could start a delimiter,          |
|         in a given recognition mode.                                        |
|         This table has been allocated by the caller, and set to             |
|         binary zeroes.                                                      |
|                                                                             |
|  Build dvtab:                                                               |
|                                                                             |
|  dvtab: (Delimiter Vectors table)                                           |
|         - update the temporary KYWD delim table, while sorting it,          |
|         - pass it to "get_stub", that build a temporary tree structure,     |
|         - call "prim_postorder", to traverse the tree and build the         |
|           final dvtab.                                                      |
|                                                                             |
|  Fill entries in dvtbent (8 entries, one for each mode)                     |
|                                                                             |
+----------------------------------------------------------------------------*/
DV * P_FCT dlm_dvtb(COMMON_P M_p, DVTBENT *new_dvtbent_p)
{
   GA_P gbla_p = M_p->gbla_p;
   uint i, j, kywd_count;
   uchar *c1_p, *c2_p;
   ALLMO allmode;
   KYWD  *kywd_tb;
   DDLMV const *ddlmv_p;
   KYWD  kywd;
   TREE   HEAD, *TR_p;
   LOCAL  L;
   boolean do_install;

   for (ddlmv_p=GA(ddlmv_tab), i=0; i < NBR_OF_DDLMV; ++i, ++ddlmv_p) {
      /*-------------------------------+
      |Install the combined delimiters |
      +-------------------------------*/
      allmode = ddlmv_p->allmode;
      c1_p = M_p->ddlmv_str[i].s1;
      c2_p = M_p->ddlmv_str[i].s2;
      switch(kywd.token = ddlmv_p->token) {     /* contextual constraints */
      case STAGO_TAGC_TK:                       /* GI:  */
      case ETAGO_TAGC_TK:                       /* GI:  */
         do_install=IS_BITON(M_p->opt.ft,SHORTTAG_FT) ? TRUE: FALSE;
         break;
      case STAGO_GRPO_TK:                       /* GI:  */
      case ETAGO_GRPO_TK:                       /* GI:  */
         do_install=IS_BITON(M_p->opt.ft,CONCUR_FT) ? TRUE: FALSE;
         break;
      case ERO_GRPO_TK:                         /* NMS: */
      case PERO_GRPO_TK:                        /* NMS: */
         do_install=
            IS_BITON(M_p->opt.ft,CONCUR_FT|SIMPLE_FT|EXPLICIT_FT|IMPLICIT_FT) ?
            TRUE: FALSE;
         break;
      default:
         do_install = TRUE;
         break;
      }
      if (do_install) {
         memcpy(GA(lit_p), c1_p, j=strlen(c1_p));
         memcpy(GA(lit_p)+j, c2_p, 1+strlen(c2_p));
         install_delim(M_p, &kywd, GA(lit_p), allmode);
      }
      free(c1_p);
      free(c2_p);
   }

   /*--------------------------------------------------+
   |build the DV array, used to scan delimiter keywords|
   +--------------------------------------------------*/
   HEAD.ltag = FALSE;
   HEAD.rtag = TRUE;
   HEAD.llink = HEAD.rlink = &HEAD;
   L.M_p = M_p;
   L.kywd_p = kywd_tb = ((KYWD *)sa_term(M_p->delim_id,&kywd_count));
   L.tot_dv = -(NBR_OF_MODES + 1);
   get_stub(0,0,1,kywd_count,&HEAD,kywd_tb[0].word,&L);
   L.dv_orig = L.dv_p = (DV *)malloc((L.tot_dv)*sizeof(DV));
   L.prev_bseq = 0;
   /*---------------------------------------+
   |For each mode, set up entries in dvtbent|
   +---------------------------------------*/
   TR_p = HEAD.llink;
   while (TR_p->rtag) {
      TR_p = TR_p->rlink;
      new_dvtbent_p++->rel = diffptr(L.dv_p, L.dv_orig); /* One for each mode */
      if (TR_p->ltag) prim_postorder(TR_p->llink,&L);
      free(TR_p);
   }
   free(HEAD.llink);
   /* If we need to clean the heap (?) */
   for (L.kywd_p = kywd_tb, i = kywd_count; i ;++L.kywd_p, --i) {
      free(L.kywd_p->word);
   }
   free((char *)kywd_tb);

   M_p->dv_max = L.tot_dv;
   return(L.dv_orig);
}
/******************************************************************************/
