/*******************************************************************************
* 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 16 and up                     *
* COMPONENT NAME   = dlfind.c                                                  *
* DESCRIPTIVE NAME = Delimiter Recognition                                     *
* DATE_WR          = 03/28/1987                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
* (will be filled later: see description in each module)                       *
*                                                                              *
* IMPLEMENTATION NOTE =                                                        *
* None.                                                                        *
*                                                                              *
* DEPENDENCIES =  (see Include list)                                           *
*                                                                              *
* IMPORT=                                                                      *
* em_read()                                                                    *
* em_unrd()                                                                    *
*                                                                              *
* EXPORT=                                                                      *
* dl_find()                                                                    *
* rcgn_mo                                                                      *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits    Change & Reason (brief)                             *
*      1 04/24/88 PGR      Splitted DVTAB in 8, one for each mode              *
*      2 05/08/88 PGR      Reworked and cleaned Bseq algorithm                 *
*      3 10/15/88 PGR      Changed an && into a nested if (see 10/15/88 bug)   *
*      4 10/02/89 PGR      Changed the binary search in the PCL                *
*                          for a faster sequential one.                        *
*******************************************************************************/

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

/*---------------------------+
|Local Functions Declarations|
+---------------------------*/
static boolean LP_FCT check_contextual_constraint(GA_P, TK);

/*--------------------------------------------------------------------set_mode-+
|Change the recognition mode                                                   |
|"mode" is one of xxx_MODE, defined in "gen.h"                                 |
+-----------------------------------------------------------------------------*/
void P_FCT set_mode(GA_P gbla_p, uint mode)
{
   GA(rcgn_mo) = (ALLMO)(1 << (GA(curr_mode) = mode));
   GA(dv_mo_p) = GA(dvtbent)[mode].dv_p;
}

/*--------------------------------------------------------------------psh_mode-+
|Change the recognition mode, while saving the previous one.                   |
|"mode" is one of xxx_MODE, defined in "mode.h"                                |
+-----------------------------------------------------------------------------*/
void P_FCT psh_mode(GA_P gbla_p, uint mode)
{
   if (GA(max_mode_ix) > MAX_MODE_LVL) {
      errorh(gbla_p,BIGMODLVL,ERH_T_SEV+ERH_LOCATE);
   }
   GA(mode_stack)[GA(max_mode_ix)++] = GA(curr_mode);
   GA(rcgn_mo) = (ALLMO)(1 << (GA(curr_mode) = mode));
   GA(dv_mo_p) = GA(dvtbent)[mode].dv_p;
}

/*--------------------------------------------------------------------pop_mode-+
|Restore previous recognition mode.                                            |
|"mode" is one of xxx_MODE, defined in "mode.h"                                |
+-----------------------------------------------------------------------------*/
void P_FCT pop_mode(GA_P gbla_p)
{
   GA(curr_mode) = GA(mode_stack)[--GA(max_mode_ix)];
   GA(rcgn_mo) = (ALLMO)(1 << GA(curr_mode));
   GA(dv_mo_p) = GA(dvtbent)[GA(curr_mode)].dv_p;
}

/*--------------------------------------------------------------------hid_mode-+
|Do not allow any MARKUP to be recognized                                      |
|"rst_mode" restores the hidden mode.                                          |
+-----------------------------------------------------------------------------*/
void P_FCT hid_mode(GA_P gbla_p)
{
   GA(rcgn_mo) = (ALLMO)0;
}

/*--------------------------------------------------------------------rst_mode-+
|Restore the current mode. It could have been previously hidden by "hid_mode". |
+-----------------------------------------------------------------------------*/
void P_FCT rst_mode(GA_P gbla_p)
{
   GA(rcgn_mo) = (ALLMO)(1 << GA(curr_mode));
}

/*---------------------------------------------------------------------dl_find-+
| Try to decode a delimiter at the current position.                           |
| Set up curr_count, curr_token,                                               |
| Return curr_token (could be NOT_FOUND_TK).                                   |
|                                                                              |
| IMPLEMENTATION NOTE:                                                         |
|      Due to the complexity of the Delimiters Recognition rules, with         |
|      B-sequence imbedded, the following code is a very tough one.            |
|      Change it with care !  Sloppy goto's are used to optimize               |
|      execution and (paradoxically) for readibility !                         |
|                                                                              |
| CODE ANNOTATIONS:                                                            |
|                                                                              |
| (1) Re: "A BSEQ cannot be preceded/followed by a blank"                      |
|     if dv_pA was set, this means that more than one alternate was            |
|     found, in violation of the rule stated above.                            |
|                                                                              |
| (2) Re: "only one BSEQ is allowed"                                           |
|     The BSEQ can be descended only one time, so we have no scruples          |
|     to chain the previous dv_p; this also acts as a protection for (3);      |
|                                                                              |
| (3) At this point, "in_bseq_recur" is always FALSE, so we cannot decode      |
|     a B-seq more than 2 times.  Ad absurbo:                                  |
|     - if we were flunked in the search of the first BSEQ PCL,                |
|       the scanned char is reputed to be non-blank                            |
|       (all blanks have been exhausted  during the B-seq descent).            |
|       Then: "is_blank(curr_char)" is FALSE.                                  |
|     - All other suceeding PCLs are reputed *not* to contain any B-seq,       |
|       since only one single B-seq is allowed.                                |
|       Then: "dv_p->i.h.is_bseq" is FALSE.                                    |
|     Note: if the single B-seq Rule was not existing, this would have         |
|           implied the stacking of prev_count, prev_tk, and dv_pP values.     |
|                                                                              |
+-----------------------------------------------------------------------------*/
TK P_FCT dl_find(GA_P gbla_p)
{
   register DV *dv_p;
   register uchar     *c_p;
   auto     uint       i, max_bseq, count;
   auto     boolean    in_bseq_recur;
   auto     DV        *dv_pP;                   /* Previous */
   auto     DV        *dv_pA;                   /* Alternate */
   auto     DV        *dv_pF;                   /* Found */
   auto     DV        *prev_dv_pF;              /* Previously Found */
   auto     uint       A_peg, /* F_peg, */ prev_F_peg, after_bseq_peg;
#define F_peg GA(curr_count)


   /*- To do ? --------------------------------------+
   |May be: returns EE_TK or NONSGML_TK              |
   |        CHAR_TK has more sense that NOT_FOUND_TK |
   +------------------------------------------------*/

   F_peg = 0;                                   /* <=== curr_count = 0 */
   if (!(GA(dltab)[GA(curr_char)] & (GA(rcgn_mo)))) {
      return(GA(curr_token)=NOT_FOUND_TK);
   }
   dv_p = GA(dv_mo_p);
   count = 0;
   in_bseq_recur = FALSE;
   dv_pA = (DV *)0;
   dv_pF = (DV *)0;

main_loop: /*-----------------------------------------------------------------*/
   /*---------------------------------------------------+
   |Search thru the dv_p structure the "best" delimiter |
   +---------------------------------------------------*/
   if ((c_p = memchr(dv_p->s.str,GA(curr_char),dv_p->i.h.count))!=(uchar *)0) {
      /*----------------------------------------------------+
      |Found in the PCL: set up any alternate, and continue |
      +----------------------------------------------------*/
      if ((is_blank(GA(curr_char)))&&  /* Blank conflict ? (alternate found) */
         (dv_p->i.h.is_bseq)&&         /* see note (3) */
         (!dv_pA)) {                   /* PROTECTION: see Note (1) */
         dv_pA = dv_p;                 /* yes, set alternate path */
         A_peg = count;                /* remember where */
      }
      em_read(gbla_p);                 /* new curr_char */
      ++count;                         /* increase unread count */
      dv_p += 1 + c_p - dv_p->s.str;   /* get 2nd node */

examine_cont_string: /*-------------------------------------------------------*/
      /*------------------------------------+
      |Check the continuation string matches|
      +------------------------------------*/
      c_p = dv_p->s.str - 1;           /* point continuation string */
      while (((*++c_p))&&(*c_p==(uchar)GA(curr_char))) { /* leave at eos */
         ++count;                      /* increase unread count*/
         em_read(gbla_p);              /* look for a match */
      }
      if (*c_p) {                      /* eos not found (it's bad): */
         if (in_bseq_recur) goto try_another_B_dv;
         goto break_it;                /* no recursion: leave */
      }                                /* eos found (it's ok): */
      if (dv_p->t.tk) {                /* largest token gotten */
         if (check_contextual_constraint(gbla_p,dv_p->t.tk)) { /*10/15/88 fix*/
            dv_pF = dv_p;              /* get its DV */
            F_peg = count;
         }                             /* else, as not found... */
         if (in_bseq_recur) goto try_another_B_dv;
         goto break_it;                /* and leave... */
      }
      if (((dv_p=dv_p->i.next)->t.tk) &&  /* may be found */
         check_contextual_constraint(gbla_p,dv_p->t.tk)) {
         dv_pF = dv_p;                 /* set may_be DV */
         F_peg = count;                /* peg unread here */
      }
      goto main_loop;
   }
   if (in_bseq_recur) goto try_another_B_dv;  /* see note (2) */
   /*---------------------------------+
   |Not found in the PCL, first time: |
   |try a blank seq recognition       |
   +---------------------------------*/
   if ((is_blank(GA(curr_char)))&&  /* need a Bseq, */
      (dv_p->i.h.is_bseq)) {        /* and an alternate path */
      goto decode_Bseq;             /* see note (3) */
   }
   goto break_it;

try_another_B_dv: /*----------------------------------------------------------*/
   /*----------------------------------------------------------------+
   |Not found: while doing blank sequence nodes examinition,         |
   |go to another previous B-dv if needed and possible (see note (2) |
   |For a same count, prev_F is always more specific..               |
   +----------------------------------------------------------------*/
   if (F_peg > prev_F_peg) {                /* between both... */
      prev_F_peg = F_peg ;                  /* choose the longest */
      prev_dv_pF = dv_pF ;
   }
   if (!(dv_p=dv_pP=(dv_pP+1)->i.previous)) { /* not possible */
      F_peg = prev_F_peg;                     /* set final answer */
      dv_pF = prev_dv_pF;
      goto break_it;
   }
   em_unrd(gbla_p,count - after_bseq_peg);  /* back where we were */
   count = after_bseq_peg;
   goto examine_cont_string;

decode_Bseq: /*---------------------------------------------------------------*/
   for (;;) {
      /*-------------------------------------------+
      |descend thru the maximum of B-sequence node |
      +-------------------------------------------*/
      dv_p += dv_p->i.h.count + 1;           /* point BSEQ node */
      i = (dv_p+1)->t.bseqlen;               /* get minimum BSEQ length */
      max_bseq = count + BSEQLEN;
      do {                                   /* skip minimum blanks: */
         ++count;
         em_read(gbla_p);
         if (!(is_blank(GA(curr_char)))) break;
      }while (--i);
      switch(i) {
      case 0:                                /* more than min blanks found */
         if ((dv_p+1)->s.must_recur) {       /* more B specificity: */
            dv_p = dv_p->i.next;             /* chain next node */
            continue;                        /* try it... */
         }
         do {                                /* no more B-sequence node: */
            em_read(gbla_p);                 /* skip all others blanks */
            if (++count >=max_bseq) break;   /* no more than BSEQLEN ! */
         }while (is_blank(GA(curr_char)));
         break;
      case 1:                                /* exactly min blanks found */
         break;
      default:                               /* less than min blanks found */
         dv_p = (dv_p+1)->i.previous;        /* back to previous */
         break;                              /* we'll check dv_p after */
      }
      break;
   }
   if ((dv_pP = dv_p)!= (DV *)0) {
      in_bseq_recur = TRUE;
      prev_F_peg = F_peg;                    /* save F_peg */
      prev_dv_pF = dv_pF;                    /* and dv_pF */
      after_bseq_peg = count;                /* peg unread(s) here */
      goto examine_cont_string;
   }

break_it: /*------------------------------------------------------------------*/
   if (dv_pA) {                                 /* Alternate Bseq co-exists */
      em_unrd(gbla_p,count - A_peg);            /* unread to it */
      count = A_peg;
      dv_p = dv_pA;                             /* go try it */
      dv_pA = (DV *)0;
      goto decode_Bseq;                         /* one time only....*/
   }
   if (count != F_peg) {
      if (count < F_peg) {
         while (++count < F_peg) em_read(gbla_p); /* read one less */
         em_read(gbla_p);                       /* set curr char for the last */
      }else {
         em_unrd(gbla_p,count - F_peg);         /* unread */
      }
   }
   /* F_peg has the length of the decoded string */
   /* curr_count = F_peg;    useless, since F_peg #defined as curr_count */
   return(GA(curr_token) = (dv_pF ? dv_pF->t.tk : NOT_FOUND_TK));
}

/*STATIC-------------------------------------------check_contextual_constraint-+
|                                                                              |
+-----------------------------------------------------------------------------*/
static boolean LP_FCT check_contextual_constraint(GA_P gbla_p, TK tk)
{
   switch(tk) {
   default:
      return(TRUE);                             /* ---  any character type */
   case MSC_MDC_TK:
      /*---------------------------------------------+
      |MSC_MDC cannot be recognized as such if it    |
      |occurs in an RCDATA marked section at a higher|
      |entity level than the one the marked section  |
      |started with (see also msd_psh in SKIP.C)     |
      +---------------------------------------------*/
      if ((GA(rcgn_mo) & RCDMS_MO) && (GA(enty_level) > GA(ms_enty_level))) {
         return(FALSE);
      }else {
         return(TRUE);
      }
   case CRO_TK:                                 /* CREF digit or namestart */
      if (is_digit(GA(curr_char))) return(TRUE);  /* else fall thru */
   case ERO_TK:                                 /* NMS  namestart */
   case MDO_TK:                                 /* DCL  namestart */
   case PERO_TK:                                /* NMS  namestart */
      return (is_namestrt(GA(curr_char)) ? TRUE:FALSE);
   case STAGO_TK:                               /* GI   namestart, or 7.4.1.2 */
   case ETAGO_TK:                               /* GI   namestart, or 7.4.5.2 */
      /*---------------------------------------------+
      |if in TAG_MODE                                |
      |   always TRUE (no constraints)               |
      |   (see ISO 8879, 7.4.1.2 and 7.4.5.2)        |
      |else (CON_MO, RCD_MO, FCD_MO)                 |
      |   TRUE iff followed by a name_start char     |
      |   except for RCDATA-content, if the          |
      |   entity has been imbedded.                  |
      |                                              |
      |Note that STAGO is dl_found only for CON_MO   |
      |     (not for RCD_MO, neither FCD_MO)         |
      +---------------------------------------------*/
      if (GA(rcgn_mo) & TAG_MO) return(TRUE);
      if ((!(is_gistrt(GA(curr_char)))) ||
         ((GA(rcgn_mo) & RCD_MO) && (GA(enty_level) > GA(cn_enty_level)))) {
         return(FALSE);
      }else {
         return TRUE;
      }
   case ETAGO_TAGC_TK:
   case ETAGO_GRPO_TK:
   case NET_TK:                                 /* ELEM (special) */
      if ((GA(rcgn_mo) & RCD_MO) && (GA(enty_level) > GA(cn_enty_level))) {
         return(FALSE);
      }else {
         return(TRUE);
      }
#ifdef REMOVED_08_06_88
      /*---------------------------------------------+
      |if in TAG_MODE                                |
      |   always TRUE (no constraints)               |
      |else (CON_MO, RCD_MO, FCD_MO)                 |
      |   TRUE iff within an element whose           |
      |        start-tag was a net-enabling start tag|
      |** This rule has been moved, because it's not |
      |   easy to know so far if the start-tag was   |
      |   net enabling -- I don't want to share      |
      |   the TCB structure with anybody.            |
      +---------------------------------------------*/
      if (GA(rcgn_mo) & TAG_MO) return(TRUE);     /* no constraints */
      if (within an element whose start-tag was a net-enabling start tag) {
         return(TRUE);
      }else {
         return(FALSE);
      }
#endif
   }
}
/******************************************************************************/
