/*	jgofs_selection_tester.c					*/
/*		from parse v 2.2 (Apr 09)				*/
/*    Separated test code from parse code to avoid precision issues	*/
/*  Turns out it was better to REALLY avoid the precision issues, and	*/
/*  things could have stayed as one module, but by that time other	*/
/*  changes had been made
/*    Rename source modules to avoid having a "test.c"			*/
/*    Start new versions at 2.5 for both modules			*/

/*	parse.c								*/
/*    parse and test sections from outer v 3.0a	 Jun 07			*/
/*    Separated out so that inners can parse selections.  Of interest	*/
/*  for optimization purposes to be sure that vars in selections are	*/
/*  not projected out prematurely.  Turns out this premature stuff	*/
/*  would help outer, too, and is much more critical there! (Apr 09)	*/

#define JG_SEL_TESTER_VERSION "jg_sel_test version 2.5a 21 Nov 2011"

/*  21 Nov 2011.  wjs							*/
/*	Put "version" in version name so  strings | grep version  works	*/
/*    more reliably							*/
/*		[Begin v 2.5a]						*/
/*  20 May 2009.  wjs							*/
/*	Check a couple of stack sizes					*/
/*	Do some restructuring						*/
/*	Split tester from parser (see above)				*/
/*	Remove precision-dependent code					*/
/*		[Needs parse.h v 2.2]					*/
/*		[Begin v 2.5]						*/
/*	[Comments below from parse.  See jgofs_selection_parser.c for	*/
/*	 older comments that might apply to jgofs_selection_tester]	*/
/*   3 Apr 2009.  wjs							*/
/*	Modify jgofs_selection_test to return "Unknown" when it's 	*/
/*    unknown if record	matches selection criteria.  This happens	*/
/*    when criteria are at a lower level than the record being		*/
/*    tested.  Previous behavior was to try to return 			*/
/*    "accept record" in such cases.  The attempt did not succeed	*/
/*    in cases where the "not" logical operator was used		*/
/*	Increase size of stack in anticipation of more parenthesized	*/
/*    stuff from the OOserver subselection page				*/
/*	Change jgofs_selection_test function type to Logical from int	*/
/*		[Needs parse.h v 2.1]					*/
/*		[Begin v 2.2]						*/

/*
P-code form:
128..255 exec test(n-128) [negative #'s on Sun]
			  [negativity not used in this code-fortunately.  WJS]
1=or,2=and,3=not
0: end
*/

#include "parse.h"

/*  Function in parse_utils						*/
int strlike();

/*  Function in inner 							*/
void iovalstr_();
/*  Function in inner (most) or possibly a dummy that calls iovalreal_	*/
void iovaldouble_();

/*  To allow parse to be used standalone, we don't want inner or	*/
/*  outer functions (like err) to be used by parse.  Such functions	*/
/*  are in the parse_functions data structure				*/
/*    We could probably get away w/using the inner/outer versions here	*/
/*  in test, but it's easier to just use the parse_functions structure	*/

char *jgofs_selection_tester_return_vers()
/*  Dummy routine.  Exists only to force .h file version string into	*/
/*  this module.  Note string must not be global or we'll have con-	*/
/*  flicts if another routine similarly includes the version string	*/
{
  static char version[] = JG_SEL_TESTER_VERSION"/"FULL_PARSEH_VERSION;
  return version;
}

Logical jgofs_selection_tester(cl,parse_data_struct,parse_function_struct)
int cl;
struct parse_data *parse_data_struct;
struct parse_functions *parse_function_struct;
{
double f;
static char tmp[DATUMSIZE+1],uptmp[DATUMSIZE+1];
int i,j,m;
char *datum_ptr,*test_condition_ptr;
  /*  Size below comes from conclusion that in tstproccnt loop below,	*/
  /*  the stack pointer could be incremented once each interaction,	*/
  /*  even though logically, I think the pointer is supposed to end up 0 */
Logical stk[N_JG_SEL_OPERATIONS];
int stkp;


  /*  Stuff from parse interface structures				*/
    /*  Data								*/
int tstcnt;
int tstproccnt;
struct parse_test_struct *tst;
int *tstproc;

tstcnt = parse_data_struct->tstcnt;
tstproccnt = parse_data_struct->tstproccnt;
tst = parse_data_struct->tst;
tstproc = parse_data_struct->tstproc;

#ifdef DEBUG
  printf("entered test at level %d\n",cl);
#endif
stkp = -1;
stk[0] = TRUE;
for (i=0; i<tstproccnt; i++) {
  if (tstproc[i] >= 128) {
    j = tstproc[i]-128;
      /*  We have a relational test to perform, whose result will be	*/
      /*  put on the stack.  The result depends on whether we "have the	*/
      /*  data available"; ie, the level we're at.  No matter what,	*/
      /*  though, we will add to the stack, so check that up top	*/
    if (++stkp == N_JG_SEL_OPERATIONS) parse_function_struct->errn
      ("Internal error: Temp stack overflow.  Max =",N_JG_SEL_OPERATIONS);

      /*  Use previously saved test result-no need to recalc it		*/
    if (tst[j].testlev < cl) stk[stkp] = tst[j].testres;

      /*  Test is at level we haven't gotten to yet.  Return true even	*/
      /*  if data at next level might not pass test.  Therefore, all	*/
      /*  level N data shows up when displaying level N, but might not	*/
      /*  when displaying same object at level N+1.  Only a problem	*/
      /*  when displaying by level...					*/
      /*	Too bad that putting TRUE on the stack doesn't mean	*/
      /*  that TRUE gets returned... esp if next thing on stack is NOT!	*/
    if (tst[j].testlev > cl) stk[stkp] = NOT_VALID;

      /*  We are at the level where the test needs to happen - do it	*/
    if (tst[j].testlev == cl) {
      if (tst[j].testcode < 0) { 
	iovalstr_(&(tst[j].testvar),tmp);
	if (tst[j].case_sensitive) {
	  datum_ptr = tmp;
	  test_condition_ptr = tst[j].teststr;
	} else {
	  m = 0;	
	  while (tmp[m] != '\0') {
	    uptmp[m] = toupper(tmp[m]);
	    m++;
	  }
	  uptmp[m] = '\0';
	  datum_ptr = uptmp;
	  test_condition_ptr = tst[j].upteststr;
	}
	if (tst[j].testcode >= -6) 
	  f = (double) strcmp(datum_ptr,test_condition_ptr);
      } else {
	iovaldouble_(&(tst[j].testvar),&f);
      }

	/*  Suspect that abs in next line not needed.  Comments in	*/
	/*  parse spoke of ">128 neg #s on SUN", followed by later	*/
	/*  comment that this feature not used				*/
      switch (abs(tst[j].testcode)) {
	case 1:
	  m = (f < tst[j].testval);
	  break;
	case 2:
	  m = (f == tst[j].testval);
	  break;
	case 3:
	  m = (f > tst[j].testval);
	  break;
	case 4:
	  m = (f <= tst[j].testval);
	  break;
	case 5:
	  m = (f != tst[j].testval);
	  break;
	case 6:
	  m = (f >= tst[j].testval);
	  break;
	case 7:					/*  contains		*/
	  m = (strstr(datum_ptr,test_condition_ptr) != NULL);
	  break;
	case 8:					/*  is_contained_in	*/
	  m = (strstr(test_condition_ptr,datum_ptr) != NULL);
	  break;
	case 9:					/*  begins_with		*/
	  m = (strncmp(datum_ptr,test_condition_ptr,tst[j].lenteststr) == 0);
	  break;
	case 10:				/*  ends_with		*/
	  m = strlen(datum_ptr) - tst[j].lenteststr;
	  m = (m >= 0) ? (strcmp(datum_ptr+m,test_condition_ptr) == 0) : FALSE;
	  break;
	case 11:				/*  like		*/
	  m = (strlike(datum_ptr,
		       test_condition_ptr,
		       parse_function_struct->err) 
		== 0);
	  break;
	default:
	  parse_function_struct->errn
	  	("Internal error-illegal testcode",tst[j].testcode);
      }
      tst[j].testres = m;
      stk[stkp] = m;    
    }
 
 } else {
    switch (tstproc[i]) {
	/*  Note that we are in a ternary logic situation here.  Value	*/
	/*  on stack can be TRUE, FALSE, or NOT_VALID			*/
      case 1:	/* 	OR operator					*/
	if ((stk[stkp-1] == TRUE) || (stk[stkp] == TRUE)) {
	  stk[stkp-1] = TRUE;
        } else if ((stk[stkp-1] == FALSE) && (stk[stkp] == FALSE)) {
	  stk[stkp-1] = FALSE;
	} else {
	  stk[stkp-1] = NOT_VALID;
	}
	stkp--;
	break;
      case 2:	/* 	AND operator					*/
	if ((stk[stkp-1] == FALSE) || (stk[stkp] == FALSE)) {
	  stk[stkp-1] = FALSE;
        } else if ((stk[stkp-1] == TRUE) && (stk[stkp] == TRUE)) {
	  stk[stkp-1] = TRUE;
	} else {
	  stk[stkp-1] = NOT_VALID;
	}
	stkp--;
	break;
      case 3:	/* 	NOT operator					*/
	  /*  If value on stack is NOT_VALID, negating it also results	*/
	  /*  in NOT_VALID, so we don't need to change anything on the	*/
	  /*  stack							*/
	if (stk[stkp] != NOT_VALID) stk[stkp] = ( ! stk[stkp]);
	break;
      default:
	parse_function_struct->errn
		("Internal error-illegal tstproc",tstproc[i]);
    }
  }
}
#ifdef DEBUG
  printf("test result = %d\n",stk[0]);
#endif

  /*  Don't know if test changes the next 2 or if  these arrays		*/
  /*  are even meaningful after a run through test - too lazy to check	*/
parse_data_struct->tstcnt = tstcnt;
parse_data_struct->tstproccnt = tstproccnt;

if ((tstproccnt != 0) && (stkp != 0)) parse_function_struct->errn
  ("Internal error-selection result stack has >1 entry. Size",stkp+1);
return stk[0];
}
