/* e4parse.c   (c)Copyright Sequiter Software Inc., 1990-1994.  All rights reserved. */

/* Restrictions - STR can only have a constant 2nd & 3rd parameters
   SUBSTR can only have a constant 2nd & 3rd parameters
   LEFT can only have a constant 3rd parameter
   IIF must return a predictable length and type
   TRIM and LTRIM returns an unpredictable length.  Its result
   can be operated on by the concatenate operator only.
   Ex. TRIM(L_NAME) + TRIM(F_NAME) is OK
   SUBSTR( TRIM(L_NAME), 3, 2 ) is not OK
   Memo field's evaluate to a maximum length.  Anything over
   this maximum gets truncated.
   */

#include "d4all.h"
#ifdef __TURBOC__
#pragma hdrstop
#endif

#include <ctype.h>

void e4function_pop( EXPR4 * ) ;

/* e4massage
   -  Check the type returns to ensure that functions get the correct type
   result.  Use 'E4FUNCTIONS.code' to change the function where possible
   so that the correct function is used.
   -  Make sure that field parameters are put on the stack for the concatentate
   operators.
   -  Fill in the function pointers.
   -  Change (FIELD4 *) pointers in 'p1' to (char *) pointers.
   -  Where the result stack is used, make sure the correct values are filled
   into the E4INFO entires in order to adjust for the lengths needed.
   -  Check the length returns to make sure that 'code_base->expr_buf_len' is large enough
   to handle executing the expression.
   -  Calculate the length of the final result.
   -  Enforce restrictions to TRIM, STR and IIF
   -  Make sure an extra max. length character is added for e4upper() & e4trim()
   */

static int e4massage( E4PARSE *p4 )
{
  E4INFO *info ;
  int parm_pos, i_parm, is_ok, code_on, i_info, num_parms ;
  int type_should_be, len, length_status, i, done_trim_memo_or_calc ;
  int position[E4MAX_STACK_ENTRIES+1] ;
  long length[E4MAX_STACK_ENTRIES] ;
  long buf_len_needed ;
  int types[E4MAX_STACK_ENTRIES] ;
  int num_entries[E4MAX_STACK_ENTRIES] ;
  E4INFO *pointers[E4MAX_STACK_ENTRIES] ;
  CODE4 *code_base ;
  unsigned stored_key_len ;

  code_base = p4->code_base ;
  num_parms = done_trim_memo_or_calc = 0 ;
  buf_len_needed = 0 ;

  position[0] = 0 ; /* The first parameter can be placed at position 0 */

  for( i_info = 0; i_info < p4->expr.info_n; i_info++ )
  {
    info = p4->expr.info + i_info ;

    /* Check the parameters types */
    code_on = v4functions[info->function_i].code ;
    if ( v4functions[info->function_i].num_parms != (char)info->num_parms )
      if ( v4functions[info->function_i].num_parms > 0 )
      {
        if( code_base->expr_error )
          e4( code_base, e4num_parms, p4->expr.source ) ;
        return -1 ;
      }

    for(;;)
    {
      if ( code_on != v4functions[info->function_i].code )
      {
        if( code_base->expr_error )
          e4( code_base, e4type_sub, p4->expr.source ) ;
        return -1 ;
      }

      is_ok = 1 ;

      for( i_parm = 0; i_parm < info->num_parms; i_parm++ )
      {
        if ( (int)v4functions[info->function_i].num_parms < 0 )
          type_should_be = v4functions[info->function_i].type[0] ;
        else
          type_should_be = v4functions[info->function_i].type[i_parm] ;

        parm_pos = num_parms - info->num_parms + i_parm ;

        if ( types[parm_pos] != type_should_be )
        {
          if ( types[parm_pos] == r4date && type_should_be == r4date_doub )
          {
            pointers[parm_pos]->function_i = E4FIELD_DATE_D ;
            length[parm_pos] = sizeof(double) ;
            continue ;
          }
          if ( types[parm_pos] == r4num && type_should_be == r4num_doub )
          {
            pointers[parm_pos]->function_i = E4FIELD_NUM_D ;
            length[parm_pos] = sizeof(double) ;
            continue ;
          }

          info->function_i++ ;
          is_ok = 0 ;
          break ;
        }
      }
      if ( is_ok )
        break ;
    }

    switch( info->function_i )
    {
    case E4CONCATENATE:
    case E4CONCAT_TWO:
    case E4TRIM:
    case E4LTRIM:
    case E4UPPER:
    case E4SUBSTR:
    case E4LEFT:
#ifdef S4CLIPPER
    case E4DESCEND_STR:
      /*          case E4DESCEND_STR+1: */
#endif
#ifndef S4MEMO_OFF
    case E4FIELD_MEMO:
#endif
      for( i_parm = 1; i_parm <= info->num_parms; i_parm++ )
      {
        E4INFO *info_parm = pointers[num_parms-i_parm] ;
        if ( info_parm->function_i == E4FIELD_STR )
          /* Make sure the parameter is put on the stack. */
          info_parm->function_i = E4FIELD_STR_CAT ;
        if ( info->function_i == E4CONCATENATE  &&  done_trim_memo_or_calc )
          info->function_i = E4CONCAT_TRIM ;
      }
      break ;
    default:
      break ;
    }

    num_parms -= info->num_parms ;
    if ( num_parms < 0 )
    {
      if( code_base->expr_error )
        e4( code_base, e4result, 0 ) ;
      return -1 ;
    }

    types[num_parms] = v4functions[info->function_i].return_type ;

    if ( info->function_i == E4CALC_FUNCTION )
      types[num_parms] = expr4type( ((EXPR4CALC *) info->p1)->expr ) ;
    switch( types[num_parms] )
    {
    case r4str:
      switch( info->function_i )
      {
      case E4FIELD_STR:
      case E4FIELD_STR_CAT:
        length[num_parms] = f4len( info->field_ptr ) ;
        break ;

#ifndef S4MEMO_OFF
      case E4FIELD_MEMO:
        length[num_parms] = code_base->mem_size_memo_expr ;
        done_trim_memo_or_calc = 1 ;
        break ;
#endif  /* S4MEMO_OFF */

      case E4CONCATENATE:
      case E4CONCAT_TWO:
      case E4CONCAT_TRIM:
        info->i1 = (int) (length[num_parms]) ;
        length[num_parms] += length[num_parms+1] ;
        break ;

      case E4IIF:
        if ( length[num_parms+1] != length[num_parms+2] )
        {
          if( code_base->expr_error )
            e4describe( code_base, e4length_err, p4->expr.source, 0, 0 ) ;
          return -1 ;
        }
        length[num_parms] = length[num_parms+1] ;
        break ;

      case E4DTOS:
      case E4DTOS+1:
        length[num_parms] = 8 ;
        break ;

      case E4DTOC:
      case E4DTOC+1:
      case E4CTOD:
        length[num_parms] = 8 ;
        info->i1 = p4->constants.pos ;
        len = strlen( code_base->date_format) ;
        s4stack_push_str( &p4->constants, code_base->date_format,
                         len + 1 ) ;
        if ( info->function_i == E4DTOC || info->function_i == E4DTOC+1 )
          length[num_parms] = len ;
        break ;

      case E4DEL:
        length[num_parms] = 1 ;
        info->p1 = (char *)&p4->expr.data->record ;
        break ;

      case E4CALC_FUNCTION:
        done_trim_memo_or_calc = 1 ;
        length[num_parms] = expr4len( ((EXPR4CALC *) info->p1)->expr ) ;
        break ;

      case E4SUBSTR:
      case E4LEFT:
        if ( info->i1 > (int)(length[num_parms]) )
          info->i1 = (int)(length[num_parms]) ;
        if ( info->i1 < 0 )
          info->i1 = 0 ;
        length[num_parms]  -= info->i1 ;
        if ( info->len > (int)(length[num_parms]) )
          info->len = (int)(length[num_parms]) ;
        length[num_parms] = info->len ;
        break ;

      case E4TIME:
        length[num_parms] = 8 ;
        break ;

      case E4TRIM:
      case E4LTRIM:
        done_trim_memo_or_calc = 1 ;
        p4->expr.has_trim = 1 ;
        break ;

      case E4UPPER:
      case E4DESCEND_STR:
        /*             case E4DESCEND_STR+1: */
        break ;

      default:
        length[num_parms] = info->len ;
      }
      break ;

    case r4num:
      length[num_parms] = f4len( info->field_ptr ) ;
      break ;

    case r4num_doub:
    case r4date_doub:
      length[num_parms] = sizeof(double) ;
      if ( info->function_i == E4CTOD )
      {
        info->i1 = p4->constants.pos ;
        s4stack_push_str( &p4->constants, code_base->date_format,
                         strlen( code_base->date_format) + 1 ) ;
      }
      if ( info->function_i == E4RECCOUNT || info->function_i == E4RECNO )
        info->p1 = (char *) p4->expr.data ;
      break ;

    case r4date:
      length[num_parms] = 8 ;
      break ;

    case r4log:
      if ( info->function_i != E4FIELD_LOG )
      {
        if ( info->function_i == E4DELETED )
          info->p1 = (char *) &p4->expr.data->record ;
        else
        {
          info->i1 = (int)(length[num_parms+1]) ;
          length_status = 1 ;
          if ( length[num_parms] < length[num_parms+1] )
          {
            info->i1 = (int)(length[num_parms]) ;
            length_status = -1 ;
          }
          if ( length[num_parms] == length[num_parms+1] )
            length_status = 0 ;

          if ( info->function_i == E4GREATER )
          {
            if ( length_status > 0 )
              info->p1 = (char *)1L ;
            else
              info->p1 = (char *)0 ;
          }
          if ( info->function_i == E4LESS )
          {
            if ( length_status < 0 )
              info->p1 = (char *)1L ;
            else
              info->p1 = (char *)0 ;
          }
        }
      }
      length[num_parms] = sizeof(int) ;
      break ;

    default:
#ifdef S4DEBUG
      return e4( code_base, e4result, 0 ) ;
#else
      return -1 ;
#endif
    }


    /* make sure there is enough key space allocated for the type,
       in case a partial evaluation occurs */
    switch( types[num_parms] )
    {
#ifdef S4FOX
    case r4num:
    case r4date:
    case r4num_doub:
      stored_key_len = sizeof( double ) ;
      break ;
#endif  /*  ifdef S4FOX      */
#ifdef S4CLIPPER
    case r4num:  /* numeric field return, must fix length problem */
      stored_key_len = f4len( info->field_ptr ) ;
      break ;
    case r4num_doub:
      stored_key_len = code_base->numeric_str_len ;
      break ;
#endif  /*  ifdef S4CLIPPER  */
#ifdef S4NDX
    case r4num:
    case r4date:
      stored_key_len = sizeof( double ) ;
      break ;
#endif  /*  ifdef S4NDX  */
#ifdef S4MDX
    case r4num:
      stored_key_len = (int)sizeof( C4BCD ) ;
      break ;
    case r4num_doub:
      stored_key_len = (int)sizeof( C4BCD ) ;
      break ;
    case r4date:
    case r4date_doub:
      stored_key_len = sizeof( double ) ;
      break ;
#endif  /* S4MDX */
    default:
      stored_key_len = (unsigned)(length[num_parms]) ;
    }

    u4alloc_again( code_base, &code_base->stored_key, &code_base->stored_key_len, stored_key_len + 1 ) ;

    if ( code_base->error_code < 0 )
      return -1 ;

    info->result_pos = position[num_parms] ;
    buf_len_needed = length[num_parms] ;
    if ( info->function_i == E4CALC_FUNCTION )
      buf_len_needed = ((EXPR4 *)info->p1)->len_eval ;
    if( buf_len_needed > INT_MAX )
    {
      e4( code_base, e4overflow, 0 ) ;
      return -1 ;
    }

    if( (types[num_parms] == r4num || types[num_parms] == r4date)  &&
       length[num_parms] < sizeof(double) )
      position[num_parms+1] = position[num_parms] + sizeof(double) ;
    else
      position[num_parms+1] = position[num_parms] + (unsigned)length[num_parms] ;
    if ( position[num_parms] + buf_len_needed > p4->expr.len_eval )
      p4->expr.len_eval = position[num_parms] + (unsigned)buf_len_needed ;
    info->len = (int)(length[num_parms]) ;

    info->num_entries = 1 ;
    for( i = 0; i < info->num_parms; i++ )
      info->num_entries += num_entries[num_parms+i] ;

    num_entries[num_parms] = info->num_entries ;
    pointers[num_parms] = info ;

    num_parms++ ;
    if ( num_parms >= E4MAX_STACK_ENTRIES )
    {
      if( code_base->expr_error )
        e4( code_base, e4overflow, 0 ) ;
      return -1 ;
    }
  }

  if ( num_parms != 1 )
  {
    if( code_base->expr_error )
      e4( code_base, e4result, 0 ) ;
    return -1 ;
  }

  for( i = 0; i < p4->expr.info_n; i++ )
  {
    info = p4->expr.info + i ;
    info->function = (S4OPERATOR S4PTR *)v4functions[info->function_i].function_ptr ;
  }

  p4->expr.len_eval += 1 ;
  if ( code_base->expr_buf_len < (unsigned)p4->expr.len_eval )
    if ( u4alloc_again( code_base, &code_base->expr_work_buf, &code_base->expr_buf_len, p4->expr.len_eval ) == e4memory )
      return -1 ;

  p4->expr.len = (int)(length[0]) ;
  p4->expr.type = types[0] ;
  return 0 ;
}

int e4add_constant( E4PARSE *p4, int i_functions, void *cons_ptr, unsigned cons_len )
{
  E4INFO *info ;

  info = e4function_add( &p4->expr, i_functions ) ;
  if ( info == 0 )
    return -1 ;
  info->i1 = p4->constants.pos ;
  info->len = cons_len ;
  return s4stack_push_str( &p4->constants, cons_ptr, (int) cons_len ) ;
}

E4INFO *e4function_add( EXPR4 *expr, int i_functions )
{
  E4INFO *info ;

  if ( (unsigned)((expr->info_n+1)*sizeof(E4INFO)) > expr->code_base->expr_buf_len )
    if ( u4alloc_again( expr->code_base, &expr->code_base->expr_work_buf, &expr->code_base->expr_buf_len, sizeof(E4INFO) * (expr->info_n+10) ) == e4memory )
      return 0 ;

  info = (E4INFO *)expr->code_base->expr_work_buf + expr->info_n++ ;

  info->function_i = i_functions ;
  info->num_parms = v4functions[i_functions].num_parms ;
  if ( info->num_parms < 0 )
    info->num_parms = 2 ;
  info->function = (S4OPERATOR S4PTR *)v4functions[i_functions].function_ptr ;
  return info ;
}

void e4function_pop( EXPR4 *expr )
{
  expr->info_n-- ;
}

EXPR4 *S4FUNCTION expr4parse( DATA4 *d4, char *expr_ptr )
{
  E4PARSE parse ;
  char    ops[128] ;
  char    constants[512] ;
  int     rc, info_len, pos_constants ;
  EXPR4 *express4 ;

#ifdef S4DEBUG
  if ( d4 == 0 || expr_ptr == 0 )
    e4severe( e4parm, E4_EXPR4PARSE ) ;
#endif

  if ( d4->code_base->error_code < 0 )
    return 0 ;

  if ( d4->code_base->expr_buf_len > 0 )
    memset( d4->code_base->expr_work_buf, 0, d4->code_base->expr_buf_len ) ;

  memset( (void *)&parse, 0, sizeof(E4PARSE) ) ;
  memset( ops, 0, sizeof(ops));

  parse.expr.data   = d4 ;
  parse.expr.source = expr_ptr ;
  parse.code_base   = d4->code_base ;
  parse.expr.code_base = d4->code_base ;

  parse.op.ptr = ops ;
  parse.op.len = sizeof(ops) ;
  parse.op.code_base = d4->code_base ;

  parse.constants.ptr = constants ;
  parse.constants.len = sizeof(constants) ;
  parse.constants.code_base = d4->code_base ;

  s4scan_init( &parse.scan, expr_ptr ) ;

  rc = expr4parse_expr( &parse ) ;
  if ( rc < 0 )
    return 0 ;

  if ( s4stack_cur( &parse.op ) != E4NO_FUNCTION )
  {
    if( parse.code_base->expr_error )
      e4( parse.code_base, e4complete, expr_ptr ) ;
    return 0 ;
  }

  parse.expr.info = (E4INFO *)parse.code_base->expr_work_buf ;
  if ( e4massage( &parse ) < 0 )
    return 0 ;

  info_len = parse.expr.info_n * sizeof(E4INFO) ;
  pos_constants = sizeof(EXPR4) + info_len ;

  express4 = (EXPR4 *)u4alloc_free( d4->code_base, pos_constants + parse.constants.len + parse.scan.len + 1 ) ;
  if ( express4 == 0 )
    return 0 ;

  memcpy( (void *)express4, (void *)&parse.expr, sizeof(EXPR4) ) ;

  express4->data = d4 ;
  express4->info = (E4INFO *)( express4 + 1 ) ;
  express4->constants = (char *) express4 + pos_constants ;
  express4->source = express4->constants + parse.constants.len ;

  memcpy( (void *)express4->info, parse.code_base->expr_work_buf, info_len ) ;
  memcpy( express4->constants, constants, parse.constants.len ) ;
  strcpy( express4->source, expr_ptr ) ;

#ifdef S4CLIPPER
  express4->key_len = parse.expr.key_len ;
  express4->key_dec = parse.expr.key_dec ;
#endif

  return express4 ;
}

/*    Looks at the input string and returns and puts a character code on the
      result stack corresponding to the next operator.  The operators all operate
      on two operands.  Ex. +,-,*,/, >=, <, .AND., ...

      If the operator is ambiguous, return the arithmatic choice.

      Returns -2 (Done), 0, -1 (Error)
      */

int  e4get_operator( E4PARSE *p4, int *op_return)
{
  char ch ;
  int  op ;

  s4scan_range( &p4->scan, 1, ' ' ) ;
  ch = s4scan_char(&p4->scan) ;
  if ( ch==0 || ch==')' || ch==',')
  {
    *op_return = E4DONE ;
    return(0) ; /* Done */
  }

  op  = e4lookup( p4->scan.ptr+p4->scan.pos, -1, E4FIRST_OPERATOR, E4LAST_OPERATOR ) ;
  if ( op < 0 )
  {
    if( p4->code_base->expr_error )
      e4( p4->code_base, e4unrec_operator, p4->scan.ptr ) ;
    return -1 ;
  }

  p4->scan.pos += v4functions[op].name_len ;
  *op_return = op ;

  return 0 ;
}

/* e4lookup, searches 'v4functions' for an operator or function.

   str - the function name
   str_len - If 'str_len' is greater than or equal to zero it contains the
   exact number of characters in 'str' to lookup.  Otherwise,
   as many as needed, provided an ending null is not reached,
   are compared.
   */

int S4FUNCTION  e4lookup( char *str, int str_len, int start_i, int end_i )
{
  char u_str[9] ;  /* Maximum # of function name characters plus one. */
  int  i ;

  u4ncpy( u_str, str, sizeof(u_str) ) ;
  c4upper( u_str ) ;

  for( i=start_i; i<= end_i; i++)
  {
    if ( v4functions[i].code < 0 )
      break ;
    if ( v4functions[i].name == 0 )
      continue ;
#ifdef S4DEBUG
    if ( v4functions[i].name_len >= (char)sizeof(u_str) )
      e4severe( e4result, E4_EXPR4LOOKUP ) ;
#endif

    if ( v4functions[i].name[0] == u_str[0] )
      if( str_len == v4functions[i].name_len || str_len < 0 )
        if (strncmp(u_str, v4functions[i].name, (size_t) v4functions[i].name_len) == 0)
          return( i ) ;
  }
  return -1 ;
}

static int op_to_expr( E4PARSE *p4 )
{
  E4INFO *info ;

  info = e4function_add( &p4->expr, s4stack_pop(&p4->op) ) ;
  if ( info == 0 )
    return -1 ;

  for(; s4stack_cur(&p4->op) == E4ANOTHER_PARM; )
  {
    s4stack_pop(&p4->op) ;
    info->num_parms++ ;
  }

  return 0 ;
}

/*
   Parses an expression consisting of value [[operator value] ...]
   The expression is ended by a ')', a ',' or a '\0'.
   Operators are only popped until a '(', a ',' or the start of the stack.
   Left to right evaluation for operators of equal priority.

   An ambiguous operator is one which can be interpreted differently
   depending on its operands.  However, its operands depend on the
   priority of the operators and the evaluation order. Fortunately, the
   priority of an ambigous operator is constant regardless of its
   interpretation.  Consequently, the evaluation order is determined first.
   Then ambiguous operators can be exactly determined.

   Ambigous operators:+, -,  >, <, <=, >=, =, <>, #

   Return

   0  Normal
   -1  Error
   */

int  expr4parse_expr( E4PARSE *p4 )
{
  int  op_value, op_on_stack ;

  if ( expr4parse_value(p4) < 0 )
    return -1 ;

  for(;;)
  {
    if ( e4get_operator(p4, &op_value) < 0 )
      return -1 ;
    if ( op_value == E4DONE )  /* Done */
    {
      while( s4stack_cur(&p4->op) != E4L_BRACKET
            && s4stack_cur(&p4->op) != E4COMMA
            && s4stack_cur(&p4->op) != E4NO_FUNCTION )
        if( op_to_expr( p4 ) < 0 )
          return -1 ;
      return( 0) ;
    }

    /* Everything with a higher or equal priority than 'op_value' must be
       executed first. (equal because of left to right evaluation order)
       Consequently, all high priority operators are sent to the result
       stack.
       */
    while ( s4stack_cur(&p4->op) >= 0 )
    {
      op_on_stack = s4stack_cur(&p4->op) ;
      if ( v4functions[op_value].priority <=
          v4functions[op_on_stack].priority)
      {
        if ( op_value == op_on_stack && (int)v4functions[op_value].num_parms < 0)
        {
          /* If repeated AND or OR operator, combine them into one with an
             extra paramter.  This makes the relate module optimization
             algorithms easier. */
          s4stack_pop(&p4->op) ;
          s4stack_push_int( &p4->op, E4ANOTHER_PARM ) ;
          break ;
        }
        else
          if( op_to_expr( p4 ) < 0 )
            return -1 ;
      }
      else
        break ;
    }

    s4stack_push_int( &p4->op, op_value) ;

    if ( expr4parse_value(p4) < 0 )
      return -1 ;
  }
}

int  expr4parse_function( E4PARSE *p4, char *start_ptr, int f_len )
{
  int f_num, num_parms, info_i1, info_len ;
  char ch ;
#ifdef S4UNIX
  double doub_val ;
#endif
  E4INFO *info ;
  void *new_or_total_ptr = 0 ;
  EXPR4CALC *calc ;
  info_i1 = info_len = 0 ;

  if ( p4->code_base->error_code < 0 )
    return -1 ;

  f_num = e4lookup( start_ptr, f_len, E4FIRST_FUNCTION, 0x7FFF) ;
  if( f_num < 0 )
  {
    new_or_total_ptr = calc = expr4calc_lookup( p4->code_base, start_ptr, f_len ) ;
    if( calc == 0 )
    {
      if( p4->code_base->expr_error )
        e4( p4->code_base, e4unrec_function, p4->scan.ptr ) ;
      return -1 ;
    }
    else
    {
      f_num = E4CALC_FUNCTION ;
      if( calc->total != 0 )
      {
        f_num = E4TOTAL ;
        new_or_total_ptr = calc->total ;
      }
    }
  }

  s4stack_push_int( &p4->op, E4L_BRACKET ) ;
  p4->scan.pos++ ;

  num_parms = 0 ;
  for(;;)
  {
    ch = s4scan_char( &p4->scan ) ;
    if ( ch == 0 )
    {
      if( p4->code_base->expr_error )
        e4( p4->code_base, e4right_missing, p4->scan.ptr ) ;
      return -1 ;
    }
    if ( ch == ')')
    {
      p4->scan.pos++ ;
      break ;
    }

    if ( expr4parse_expr(p4) < 0 )
      return -1 ;
    num_parms++ ;

    while( s4scan_char( &p4->scan ) <= ' ' &&
          s4scan_char( &p4->scan ) >='\1')  p4->scan.pos++ ;

    if ( s4scan_char( &p4->scan ) == ')')
    {
      p4->scan.pos++ ;
      break ;
    }
    if ( s4scan_char( &p4->scan ) != ',')
    {
      if( p4->code_base->expr_error )
        e4( p4->code_base, e4comma_expected, p4->scan.ptr ) ;
      return -1 ;
    }
    p4->scan.pos++ ;
  }

  s4stack_pop( &p4->op ) ;  /* pop the left bracket */

  if ( f_num == E4STR )
  {
    info_len= 10 ;

    if ( num_parms == 3  )
    {
      info = (E4INFO *) p4->code_base->expr_work_buf + p4->expr.info_n -1 ;
      if ( info->function_i != E4DOUBLE )
      {
        if( p4->code_base->expr_error )
          e4( p4->code_base, e4not_constant, p4->expr.source ) ;
        return -1 ;
      }
#ifdef S4UNIX
      memcpy( (void *)&doub_val, (p4->constants.ptr + info->i1), sizeof(double) ) ;
      info_i1 = (int) doub_val ;
#else
      info_i1 = (int) *(double *) (p4->constants.ptr + info->i1) ;
#endif
      e4function_pop( &p4->expr ) ;
      num_parms-- ;
    }
    if ( num_parms == 2  )
    {
      info = (E4INFO *)p4->code_base->expr_work_buf + p4->expr.info_n -1 ;
      if ( info->function_i != E4DOUBLE )
      {
        if( p4->code_base->expr_error )
          e4( p4->code_base, e4not_constant, p4->expr.source ) ;
        return -1 ;
      }
#ifdef S4UNIX
      memcpy( (void *)&doub_val, (p4->constants.ptr + info->i1), sizeof(double) ) ;
      info_len = (int) doub_val ;
#else
      info_len = (int) *(double *) (p4->constants.ptr + info->i1) ;
#endif

      e4function_pop( &p4->expr ) ;
      num_parms-- ;
    }
    if ( info_len < 0 )
      info_len = 10 ;
    if ( info_len <= info_i1+1 )
      info_i1 = info_len - 2 ;
    if ( info_i1 < 0 )
      info_i1 = 0 ;
  }
  if ( num_parms == 3  &&  f_num == E4SUBSTR || num_parms == 2  &&  f_num == E4LEFT )
  {
    info = (E4INFO *)p4->code_base->expr_work_buf + p4->expr.info_n -1 ;
    if ( info->function_i != E4DOUBLE )
    {
      if( p4->code_base->expr_error )
        e4( p4->code_base, e4not_constant, p4->expr.source ) ;
      return -1 ;
    }
#ifdef S4UNIX
    memcpy( (void *)&doub_val, (p4->constants.ptr + info->i1), sizeof(double) ) ;
    info_len = (int) doub_val ;
#else
    info_len = (int) *(double *) (p4->constants.ptr + info->i1) ;
#endif
    e4function_pop( &p4->expr ) ;
    num_parms-- ;
  }
  if ( num_parms == 2  &&  f_num == E4SUBSTR )
  {
    info = (E4INFO *) p4->code_base->expr_work_buf + p4->expr.info_n -1 ;
    if ( info->function_i != E4DOUBLE )
    {
      if( p4->code_base->expr_error )
        e4( p4->code_base, e4not_constant, p4->expr.source ) ;
      return -1 ;
    }
#ifdef S4UNIX
    memcpy( (void *)&doub_val, (p4->constants.ptr + info->i1), sizeof(double) ) ;
    info_i1 = (int) doub_val ;
#else
    info_i1 = (int) *(double *) (p4->constants.ptr + info->i1) ;
#endif
    info_i1-- ;
    e4function_pop( &p4->expr ) ;
    num_parms-- ;
  }

  if ( p4->code_base->error_code < 0 )
    return -1 ;

  if ( num_parms != v4functions[f_num].num_parms  &&
      (int)v4functions[f_num].num_parms >= 0 )
  {
    if( p4->code_base->expr_error )
      e4describe( p4->code_base, e4num_parms, p4->scan.ptr, E4_NUM_PARMS, v4functions[f_num].name ) ;
    return -1 ;
  }

  info = e4function_add( &p4->expr, f_num ) ;
  if ( info == 0 )
    return -1 ;

  info->i1  = info_i1 ;
  info->len = info_len ;

  info->num_parms = num_parms ;
  if ( f_num == E4CALC_FUNCTION || f_num == E4TOTAL )
    info->p1 = (char *) new_or_total_ptr ;
  return 0 ;
}

int expr4parse_value( E4PARSE *p4 )
{
  FIELD4 * field_ptr ;
  char ch, *start_ptr, search_char ;
  int  i_functions, len, i_function, save_pos ;
  double d ;
  E4INFO *expr, *info ;
  DATA4 *base_ptr ;
  char b_name[11], f_name[11] ;

  if ( p4->code_base->error_code < 0 )
    return -1 ;

  s4scan_range( &p4->scan, ' ', ' ' ) ;

  /* expression */

  if ( s4scan_char( &p4->scan ) == '(')
  {
    p4->scan.pos++ ;

    s4stack_push_int( &p4->op, E4L_BRACKET) ;
    if ( expr4parse_expr(p4) < 0 )  return( -1 ) ;

    while ( s4scan_char( &p4->scan ) <= ' ' &&
           s4scan_char( &p4->scan ) != 0)   p4->scan.pos++ ;

    if ( s4scan_char( &p4->scan ) != ')' )
    {
      if( p4->code_base->expr_error )
        e4( p4->code_base, e4right_missing, p4->scan.ptr ) ;
      return -1 ;
    }
    p4->scan.pos++ ;
    s4stack_pop( &p4->op ) ;
    return( 0 ) ;
  }

  /* logical */
  if ( s4scan_char( &p4->scan ) == '.' )
  {
    i_functions = e4lookup( p4->scan.ptr+p4->scan.pos, -1, E4FIRST_LOG, E4LAST_LOG ) ;
    if ( i_functions >= 0 )
    {
      p4->scan.pos += v4functions[i_functions].name_len ;

      if ( strcmp( v4functions[i_functions].name, ".NOT." ) == 0 )
      {
        if ( expr4parse_value(p4) < 0 )  return( -1 ) ; /* One operand operation */
        s4stack_push_int( &p4->op, i_functions ) ;
        return 0 ;
      }

      expr = e4function_add( &p4->expr, i_functions ) ;
      if ( expr == 0 )
        return -1 ;
      return 0 ;
    }
  }

  /* string */
  ch = s4scan_char( &p4->scan ) ;
  if ( ch == '\'' || ch == '\"' || ch == '[' )
  {
    if ( ch == '[' )
      search_char = ']' ;
    else
      search_char = ch ;

    p4->scan.pos++ ;
    start_ptr = p4->scan.ptr + p4->scan.pos ;

    len = s4scan_search( &p4->scan, search_char ) ;
    if ( s4scan_char( &p4->scan ) != search_char )
      if ( len < 0 )
      {
        if( p4->code_base->expr_error )
          e4( p4->code_base, e4unterminated, p4->scan.ptr ) ;
        return -1 ;
      }
    p4->scan.pos++ ;

    if ( e4add_constant( p4, E4STRING, start_ptr, len ) < 0 )
      return -1 ;
    return 0 ;
  }

  /* real */
  ch = s4scan_char( &p4->scan ) ;
  if ( ch >='0' && ch <='9' || ch == '-' || ch == '+' || ch == '.' )
  {
    start_ptr = p4->scan.ptr+p4->scan.pos ;
    save_pos = p4->scan.pos ;
    p4->scan.pos++ ;
    len = 1 ;

    while( s4scan_char( &p4->scan ) >= '0' &&
          s4scan_char( &p4->scan ) <= '9' ||
          s4scan_char( &p4->scan ) == '.' )
    {
      if ( s4scan_char( &p4->scan ) == '.' )
      {
        if ( strnicmp( p4->scan.ptr+p4->scan.pos, ".AND.",5) == 0 ||
            strnicmp( p4->scan.ptr+p4->scan.pos, ".OR.",4) == 0 ||
            strnicmp( p4->scan.ptr+p4->scan.pos, ".NOT.",5) == 0 )
          break ;
        /* if the next value is a character, then we have a database
           with a number as its name/alias.  (i.e. 111.afld), since
           numerics are invalid to being a field name, MUST be a
           number if a numeric after the decimal point... */
        if ( toupper( s4scan_char( &p4->scan + 1 ) ) >= 'A' && toupper( s4scan_char( &p4->scan + 1 ) ) <= 'Z' )
        {
          p4->scan.pos++ ;   /* make sure a letter is identified below... */
          break ;
        }
      }
      len++ ;
      p4->scan.pos++ ;
    }

    /* check to see if maybe actually a database name starting with a numeric... */
    if ( toupper( s4scan_char( &p4->scan ) ) >= 'A' && toupper( s4scan_char( &p4->scan ) ) <= 'Z' )
      p4->scan.pos = save_pos ;
    else
    {
      d = c4atod( start_ptr, len ) ;
      if ( e4add_constant( p4, E4DOUBLE, &d, sizeof(d) ) < 0 )
        return -1 ;
      return 0 ;
    }
  }

  /* function or field */
  if (u4name_char(s4scan_char( &p4->scan )) )
  {
    start_ptr = p4->scan.ptr + p4->scan.pos ;

    for( len=0; u4name_char(s4scan_char( &p4->scan )); len++ )
      p4->scan.pos++ ;

    s4scan_range( &p4->scan, (char)0, ' ' ) ;

    if ( s4scan_char( &p4->scan ) == '(' )
      return expr4parse_function( p4, start_ptr, len ) ;

    base_ptr = 0 ;

#ifdef S4FOX
    if ( s4scan_char( &p4->scan ) == '.' )
    {  /* for fox, same as -> */
      if ( len > 10 )
        len = 10 ;
      memmove( b_name, start_ptr, (size_t)len ) ;
      b_name[len] = '\0' ;

      base_ptr = d4data( p4->code_base, b_name) ;
    }
#endif

    if ( s4scan_char( &p4->scan ) == '-' )
      if ( p4->scan.ptr[p4->scan.pos+1] == '>')
      {
        if ( len > 10 )
          len = 10 ;
        memmove( b_name, start_ptr, (size_t)len ) ;
        b_name[len] = '\0' ;

        base_ptr = d4data( p4->code_base, b_name) ;

        if ( base_ptr == 0 )
        {
          if( p4->code_base->expr_error )
            e4describe( p4->code_base, e4data_name, b_name, p4->scan.ptr, (char *) 0 ) ;
          return -1 ;
        }
        p4->scan.pos++ ;
      }


    if ( base_ptr != 0 )
    {
      p4->scan.pos++ ;

      start_ptr = p4->scan.ptr + p4->scan.pos ;
      for( len=0; u4name_char(s4scan_char( &p4->scan )); len++ )
        p4->scan.pos++ ;
    }
    else
      base_ptr = (DATA4 *) p4->expr.data ;

    if ( len <= 10)
    {
      memmove( f_name, start_ptr, (size_t) len ) ;
      f_name[len] = 0 ;
      field_ptr = d4field( base_ptr, f_name ) ;
      if ( field_ptr == 0 )
        return -1 ;

#ifdef S4CLIPPER
      p4->expr.key_len = field_ptr->len ;
      p4->expr.key_dec = field_ptr->dec ;
#endif

      i_function = 0 ;
      switch( field_ptr->type )
      {
      case 'N':
      case 'F':
        i_function = E4FIELD_NUM_S ;
        break ;
      case 'C':
        i_function = E4FIELD_STR ;
        break ;
      case 'D':
        i_function = E4FIELD_DATE_S ;
        break ;
      case 'L':
        i_function = E4FIELD_LOG ;
        break ;
      case 'M':
#ifdef S4MEMO_OFF
        return e4( p4->code_base, e4not_memo, E4_EXPR4PV ) ;
#else
        i_function = E4FIELD_MEMO ;
        break ;
#endif
      default:
        if( p4->code_base->expr_error )
          e4( p4->code_base, e4type_sub, E4_TYPE_UFT ) ;
        return -1 ;
      }

      info = e4function_add( &p4->expr, i_function ) ;
      if ( info == 0 )
        return -1 ;
      info->field_ptr = field_ptr ;
      info->p1 = (char *) &base_ptr->record ;
      info->i1 = field_ptr->offset ;

      return 0 ;
    }
  }

  if( p4->code_base->expr_error )
    e4( p4->code_base, e4unrec_value, p4->scan.ptr ) ;
  return -1 ;
}

int s4stack_pop( S4STACK *s4 )
{
  int ret_value ;

  ret_value = s4stack_cur(s4) ;

  if ( s4->pos >= sizeof(int) )
    s4->pos -= sizeof(int) ;
  return ret_value ;
}

int s4stack_cur( S4STACK *s4 )
{
  int pos, cur_data ;

  if ( s4->pos < sizeof(int) )
    return E4NO_FUNCTION ;
  pos = s4->pos - sizeof(int) ;
  memcpy( (void *)&cur_data, s4->ptr+pos, sizeof(int) ) ;
  return cur_data ;
}

int s4stack_push_int( S4STACK *s4, int i )
{
  return s4stack_push_str( s4, &i, sizeof(i)) ;
}

int s4stack_push_str( S4STACK *s4, void *p, int len )
{
  char *old_ptr ;

  if ( s4->code_base->error_code < 0 )
    return -1 ;

  if ( s4->pos+len > s4->len )
  {
    old_ptr = s4->ptr ;
    if ( ! s4->do_extend )
      s4->ptr = 0 ;
    else
      s4->ptr = (char *)u4alloc_free( s4->code_base, s4->len + 256 ) ;
    if ( s4->ptr == 0 )
    {
      s4->ptr = old_ptr ;
      if( s4->code_base->expr_error )
        e4( s4->code_base, e4memory, 0 ) ;
      return -1 ;
    }
    memcpy( s4->ptr, old_ptr, s4->len ) ;
    u4free( old_ptr ) ;
    s4->len += 256 ;

    return  s4stack_push_str( s4, p, len ) ;
  }
  else
  {
    memcpy( s4->ptr+s4->pos, p, len ) ;
    s4->pos += len ;
  }
  return 0 ;
}


char s4scan_char( S4SCAN *s4 )
{
  if ( s4->pos >= s4->len )
    return 0 ;
  return s4->ptr[s4->pos] ;
}

void s4scan_init( S4SCAN *s4, char *p )
{
  s4->ptr = p ;
  s4->pos = 0 ;
  s4->len = strlen(p) ;
}

int s4scan_range( S4SCAN *s4, int start_char, int end_char )
{
  int count ;

  for ( count = 0; s4->pos < s4->len; s4->pos++, count++ )
    if ( s4->ptr[s4->pos] < start_char || s4->ptr[s4->pos] > end_char )
      return count ;
  return count ;
}

int s4scan_search( S4SCAN *s4, char search_char )
{
  int count ;

  for ( count = 0; s4->pos < s4->len; s4->pos++, count++ )
    if ( s4->ptr[s4->pos] == search_char )
      return count ;
  return count ;
}

#ifdef S4VB_DOS

EXPR4 *expr4parse_v( DATA4 *d4, char *expr )
{
  return expr4parse( d4, c4str(expr) ) ;
}

#endif
