/* 
   File:        dex.c
   Author:      Andrew W. Moore,(LOGP, RECP, PLUSP, NEGP added by James)
   Created:     Fri Jan  8 10:38:18 EST 1999
   Dxscription: Propositional Dexulae

   Copyright 1999, Schenley Park Research Inc
*/

#include <math.h> /* for HUGE_VAL definition */
#include "dex.h"

/* Private prototypes */
static dex *mk_realval_dex(double realval);
static dex *mk_attnum_dex(datset *ds,int attnum);
static dex *mk_symbolval_dex(datset *ds,int attnum,int symbolval);

#ifndef AWD_ZEROTOL 
#define AWD_ZEROTOL 1.0e-12
#endif

#define e 2.718281828

char *dtype_to_name(int dtype)
{
  char *name = NULL;
  switch ( dtype )
  {
    case ATTNUM: name = "ATTNUM"; break;
    case ROW: name = "row"; break;
    case MISSING: name = "missing"; break;
    case RANDOM: name = "random"; break;
    case GAUSSIAN: name = "gaussian"; break;
    case REALVAL: name = "REALVAL"; break;
    case SYMBOLVAL: name = "SYMBOLVAL"; break;
    case IFTHENELSE: name = "IFTHENELSE"; break;
    case NOT: name = "!"; break;
    case EQ: name = "=="; break;
    case NEQ: name = "!="; break;
    case DIVIDE: name = "/"; break;
    case TIMES: name = "*"; break;
    case POWER: name = "power"; break;
    case MOD: name = "%"; break;
    case MINUS: name = "-"; break;
    case PLUS: name = "+"; break;
    case GEQ: name = ">="; break;
    case LEQ: name = "<="; break;
    case GREATER: name = ">"; break;
    case LESSTHAN: name = "<"; break;
    case AND: name = "&&"; break;
    case OR: name = "||"; break;
    case IMPLIES: name = "=>"; break;
    case IFF: name = "<=>"; break;
    case ABS: name = "abs"; break;
    case SQRT: name = "sqrt"; break;
    case SIGN: name = "sign"; break;
    case EXP: name = "exp"; break;
    case LOG: name = "log"; break;
    case LOGP:  name = "logp"; break;
    case PLUSP: name = "+p"; break;
    case RECP: name = "recp"; break;
    case NEGP: name = "-p"; break; 
    case EXPP: name = "expp"; break;
    case LOG10: name = "log10"; break;
    case PRE: name = "pre"; break;
    case SUC: name = "suc"; break;
    case DERIV: name = "deriv"; break;
    case RANKS: name = "ranks"; break;
    case SRANKS: name = "sranks"; break;
    case YEAR:   name = "year"; break;
    case YEARDAYS: name = "yeardays"; break;
    case DAYS: name = "days"; break;
    case DAYFRAC:   name = "dayfrac"; break;
    case ROUND: name = "round"; break;
    case SIN: name = "sin"; break;
    case COS: name = "cos"; break;
    default: my_error("Unknown dtype"); break;
  }
  return name;
}


/* Warning, this recursively frees all sub-components of the dex too. */
void free_dex(dex *dx)
{
  if ( dx == NULL ) my_error("Can't free NULL dex");
  if ( dx->arg1 != NULL ) free_dex(dx->arg1);
  if ( dx->arg2 != NULL ) free_dex(dx->arg2);
  if ( dx->arg3 != NULL ) free_dex(dx->arg3);
  AM_FREE(dx,dex);
}

dex *mk_empty_dex(int dtype)
{
  dex *dx = AM_MALLOC(dex);
  dx -> symbol_type = -1;
  dx -> dtype = dtype;
  dx -> attnum = -1;
  dx -> realval = -77777.77777;
  dx -> symbolval = -7777;
  dx -> arg1 = NULL;
  dx -> arg2 = NULL;
  dx -> arg3 = NULL;
  return dx;
}

static dex *mk_realval_dex(double realval)
{
  dex *dx = mk_empty_dex(REALVAL);
  dx -> realval = realval;
  return dx;
}

static dex *mk_attnum_dex(datset *ds,int attnum)
{
  dex *dx = mk_empty_dex(ATTNUM);
  dx -> symbol_type = (ds_attnum_to_symbolicp(ds,attnum)) ? attnum : -1;
  dx -> attnum = attnum;
  return dx;
}

static dex *mk_symbolval_dex(datset *ds,int attnum,int symbolval)
{
  dex *dx = mk_empty_dex(SYMBOLVAL);
  dx -> symbol_type = attnum;
  dx -> symbolval = symbolval;
  return dx;
}

/* All of the cons_xxx_dex functions make and return a new dexula,
   usually from other subdexulae. For dxample, cons_or_dex(a,b)
   makes (am_malloc's) and returns a new dex dxnoting "a v b".

   Here's a very important memory management note (and the main reason
   that prima-donna programmers get upset about not having garbage
   collection).

   The cons_xxx_dex(a,b) use the pointers to a and b directly: after
   making this call, the
   programmer should NEVER TOUCH OR FREE dex *a or dex *b again. The
   programmer should eventually free the result of this call, and then
   indirectly a and b will be recursively freed. 

   If you'd prefer to make a dex in which the subcomponents are COPIED
   IN RECURSIVELY, call mk_xxx_dex(a,b) instead. Then the result will
   contain a complete recursive copy of a and or b instead. And that means
   that the programmer can change a or b without accidently affecting
   result. The programmer must eventually dxal with freeing a and b, AND
   the result. */

dex *cons_binary_dex(dex *a,dex *b,int dtype)
{
  dex *dx = mk_empty_dex(dtype);
  dx -> arg1 = a;
  dx -> arg2 = b;
  return dx;
}

dex *cons_trinary_dex(dex *a,dex *b,dex *c,int dtype)
{
  dex *dx = mk_empty_dex(dtype);
  dx -> arg1 = a;
  dx -> arg2 = b;
  dx -> arg3 = c;
  return dx;
}

dex *cons_unary_dex(dex *a,int dtype)
{
  dex *dx = mk_empty_dex(dtype);
  dx -> arg1 = a;
  return dx;
}

/* Makes a copy of f and recursively makes a copy of all its subcomponents
   and returns the result. */
dex *mk_copy_dex(dex *dx)
{
  if (dx==NULL) return NULL;
  else
  {
    dex *newdx = AM_MALLOC(dex);
    newdx -> dtype = dx -> dtype;
    newdx -> attnum = dx -> attnum;
    newdx -> realval = dx -> realval;
    newdx -> symbolval = dx -> symbolval;
    newdx -> arg1 = (dx->arg1 == NULL) ? NULL : mk_copy_dex(dx->arg1);
    newdx -> arg2 = (dx->arg2 == NULL) ? NULL : mk_copy_dex(dx->arg2);
    newdx -> arg3 = (dx->arg3 == NULL) ? NULL : mk_copy_dex(dx->arg3);
    return newdx;
  }
}

dex *mk_binary_dex(dex *a,dex *b,int dtype)
{
  dex *dx = mk_empty_dex(dtype);
  dx -> arg1 = mk_copy_dex(a);
  dx -> arg2 = mk_copy_dex(b);
  return dx;
}

dex *mk_unary_dex(dex *a,int dtype)
{
  dex *dx = mk_empty_dex(dtype);
  dx -> arg1 = mk_copy_dex(a);
  return dx;
}

/* Makes "a <=> b" dex using the pointers to a and b directly. */
dex *cons_iff_dex(dex *a,dex *b)
{  
  return cons_binary_dex(a,b,IFF);
}

/* Makes "a <=> b" dex, including COPIES of a and b */
dex *mk_iff_dex(dex *a,dex *b)
{
  return mk_binary_dex(a,b,IFF);
}

/* Makes "a => b" dex using the pointers to a and b directly. */
dex *cons_implies_dex(dex *a,dex *b)
{  
  return cons_binary_dex(a,b,IMPLIES);
}

/* Makes "a => b" dex, including COPIES of a and b */
dex *mk_implies_dex(dex *a,dex *b)
{
  return mk_binary_dex(a,b,IMPLIES);
}

/* Makes "a v b" dex using the pointers to a and b directly. */
dex *cons_or_dex(dex *a,dex *b)
{  
  return cons_binary_dex(a,b,OR);
}

/* Makes "a v b" dex, including COPIES of a and b */
dex *mk_or_dex(dex *a,dex *b)
{
  return mk_binary_dex(a,b,OR);
}

/* Makes "a ^ b" dex using the pointers to a and b directly. */
dex *cons_and_dex(dex *a,dex *b)
{  
  return cons_binary_dex(a,b,AND);
}

/* Makes "a ^ b" dex, including COPIES of a and b */
dex *mk_and_dex(dex *a,dex *b)
{
  return mk_binary_dex(a,b,AND);
}

/* Makes "abs a" dex using the pointer to a directly. */
dex *cons_abs_dex(dex *a)
{  
  return cons_unary_dex(a,ABS);
}
/* Makes "abs a" dex, including COPY of a */
dex *mk_abs_dex(dex *a)
{
  return mk_unary_dex(a,ABS);
}

/* Makes "sqrt a" dex using the pointer to a directly. */
dex *cons_sqrt_dex(dex *a)
{  
  return cons_unary_dex(a,SQRT);
}
/* Makes "sqrt a" dex, including COPY of a */
dex *mk_sqrt_dex(dex *a)
{
  return mk_unary_dex(a,SQRT);
}

/* Makes "sign a" dex using the pointer to a directly. */
dex *cons_sign_dex(dex *a)
{  
  return cons_unary_dex(a,SIGN);
}
/* Makes "sign a" dex, including COPY of a */
dex *mk_sign_dex(dex *a)
{
  return mk_unary_dex(a,SIGN);
}

/* Makes "exp a" dex using the pointer to a directly. */
dex *cons_exp_dex(dex *a)
{  
  return cons_unary_dex(a,EXP);
}
/* Makes "exp a" dex, including COPY of a */
dex *mk_exp_dex(dex *a)
{
  return mk_unary_dex(a,EXP);
}

/* Makes "log a" dex using the pointer to a directly. */
dex *cons_log_dex(dex *a)
{  
  return cons_unary_dex(a,LOG);
}
/* Makes "log a" dex, including COPY of a */
dex *mk_log_dex(dex *a)
{
  return mk_unary_dex(a,LOG);
}

/* Makes "log10 a" dex using the pointer to a directly. */
dex *cons_log10_dex(dex *a)
{  
  return cons_unary_dex(a,LOG10);
}
/* Makes "log10 a" dex, including COPY of a */
dex *mk_log10_dex(dex *a)
{
  return mk_unary_dex(a,LOG10);
}

/* Makes "sin a" dex using the pointer to a directly. */
dex *cons_sin_dex(dex *a)
{  
  return cons_unary_dex(a,SIN);
}
/* Makes "sin a" dex, including COPY of a */
dex *mk_sin_dex(dex *a)
{
  return mk_unary_dex(a,SIN);
}

/* Makes "cos a" dex using the pointer to a directly. */
dex *cons_cos_dex(dex *a)
{  
  return cons_unary_dex(a,COS);
}
/* Makes "cos a" dex, including COPY of a */
dex *mk_cos_dex(dex *a)
{
  return mk_unary_dex(a,COS);
}

/* Makes "pre a" dex using the pointer to a directly. */
dex *cons_pre_dex(dex *a)
{  
  return cons_unary_dex(a,PRE);
}
/* Makes "pre a" dex, including COPY of a */
dex *mk_pre_dex(dex *a)
{
  return mk_unary_dex(a,PRE);
}

/* Makes "suc a" dex using the pointer to a directly. */
dex *cons_suc_dex(dex *a)
{  
  return cons_unary_dex(a,SUC);
}
/* Makes "suc a" dex, including COPY of a */
dex *mk_suc_dex(dex *a)
{
  return mk_unary_dex(a,SUC);
}

/* Makes "deriv y x" dex using the pointers to y and x directly. */
dex *cons_deriv_dex(dex *y,dex *x)
{  
  return cons_binary_dex(y,x,DERIV);
}
/* Makes "deriv y x" dex, including COPIES of y and x */
dex *mk_deriv_dex(dex *y,dex *x)
{
  return mk_binary_dex(y,x,DERIV);
}

/* Makes "ranks a" dex using the pointer to a directly. */
dex *cons_ranks_dex(dex *a)
{  
  return cons_unary_dex(a,RANKS);
}
/* Makes "ranks a" dex, including COPY of a */
dex *mk_ranks_dex(dex *a)
{
  return mk_unary_dex(a,RANKS);
}

/* Makes "sranks x y" dex using the pointer to a directly. */
dex *cons_sranks_dex(dex *x, dex *y)
{  
  return cons_binary_dex(x,y,SRANKS);
}

/* Makes "sranks x y" dex, including COPY of a */
dex *mk_sranks_dex(dex *x, dex *y)
{
  return mk_binary_dex(x,y,SRANKS);
}

/* Makes "year a" dex, using pointer to a directly */
dex *mk_year_dex(dex *a)
{
  return mk_unary_dex(a,YEAR);
}

/* Makes "yeardays a" dex, using pointer to a directly */
dex *mk_yeardays_dex(dex *a)
{
  return mk_unary_dex(a,YEARDAYS);
}

/* Makes "days a" dex, using pointer to a directly */
dex *mk_days_dex(dex *a)
{
  return mk_unary_dex(a,DAYS);
}

/* Makes "dayfrac a" dex, using pointer to a directly */
dex *mk_dayfrac_dex(dex *a)
{
  return mk_unary_dex(a,DAYFRAC);
}

/* Makes "round a" dex, using pointer to a directly */
dex *mk_round_dex(dex *a)
{
  return mk_unary_dex(a,ROUND);
}

bool eq_token(string_array *tokens,int idx,char *key)
{
  bool result;
  if ( idx >= string_array_size(tokens) )
    result = FALSE;
  else
    result = eq_string(string_array_ref(tokens,idx),key);
  return result;
}

int dex_dtype(dex *dx)
{
  if ( dx == NULL ) my_error("dex_dtype: f is NULL");
  if ( dx->dtype < 0 || dx->dtype >= NUM_DTYPES )
    my_error(mk_printf("dex_dtype: dx->dtype == %d illegal",dx->dtype));
  return dx -> dtype;  
}

dex *dex_arg1(dex *dx)
{
  int dtype;
  if ( dx == NULL ) my_error("dex_arg1: f is NULL");
  dtype = dex_dtype(dx);

  if ( dx->arg1 == NULL )
    my_error(mk_printf("This %s-type dex has a NULL arg1",
                       dtype_to_name(dtype)));

  return dx->arg1;
}

dex *dex_arg2(dex *dx)
{
  int dtype;
  if ( dx == NULL ) my_error("dex_arg2: f is NULL");
  dtype = dex_dtype(dx);
  if ( dx->arg2 == NULL )
    my_error(mk_printf("This %s-type dex has a NULL arg2",
                       dtype_to_name(dtype)));

  return dx->arg2;
}

dex *dex_arg3(dex *dx)
{
  int dtype;
  if ( dx == NULL ) my_error("dex_arg3: f is NULL");
  dtype = dex_dtype(dx);
  if ( dx->arg3 == NULL )
    my_error(mk_printf("This %s-type dex has a NULL arg3",
                       dtype_to_name(dtype)));

  return dx->arg3;
}

int dex_attnum(dex *dx)
{
  int dtype;
  if ( dx == NULL ) my_error("dex_attnum: f is NULL");
  dtype = dex_dtype(dx);
  if ( dtype != ATTNUM ) 
   my_error(mk_printf("dex_attnum: You shouldn't ask for attnum of a %s dex",
                       dtype_to_name(dtype)));
  return dx->attnum;
}
  
double dex_realval(dex *dx)
{
  int dtype;
  if ( dx == NULL ) my_error("dex_number: f is NULL");
  dtype = dex_dtype(dx);
  if ( dtype != REALVAL ) 
   my_error(mk_printf("dex_number: You shouldn't ask for number of a %s dex",
                       dtype_to_name(dtype)));
  return dx->realval;
}

char *symbol_type_to_name(datset *ds,int symbol_type)
{
  char *result = NULL;
  if ( symbol_type < 0 )
    result = "real";
  else if ( !ds_attnum_to_symbolicp(ds,symbol_type) )
    my_error("symbol_type_to_name");
  else
    result = ds_attnum_to_name(ds,symbol_type);
  return result;
}


/* dtype is the highest possible level dtype we're dxpecting */
/* Jeff guesses there is some kind of tail recursion going on here.
   You attempt to match dtype to the next token, and if you fail, you
   decrement dtype by one and recurse again.  But why not just find the
   dtype for the next token and skip this whole recursion thing?
*/
dex *mk_parse_part(datset *ds,string_array *tokens,int *idx,
                   int dtype,int symbol_type,char **r_errmess)
{
  dex *dx = NULL;

  if ( *r_errmess == NULL )
  {
    int size = string_array_size(tokens);
    char *token = (*idx > size) ? NULL : string_array_ref(tokens,*idx);
    if ( *idx >= size )
      *r_errmess = mk_printf("Undxpected end of tokens");
    if ( dtype == IFTHENELSE && eq_string(token,"if") )
    {
      dex *then_dx = NULL;
      dex *else_dx = NULL;

      *idx += 1;
      dx = mk_parse_part(ds,tokens,idx,NUM_DTYPES-1,-1,r_errmess);
      if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
        *r_errmess = mk_printf("Must have a numeric value in the if case, "
                               "not a value of attribute %s",
                               ds_attnum_to_name(ds,dx->symbol_type));

      if ( *r_errmess == NULL && !eq_token(tokens,*idx,"then") )
        *r_errmess = mk_printf("Missing then clause in if-then-else");

      if ( *r_errmess == NULL )
      {
	*idx += 1;
	then_dx = 
	  mk_parse_part(ds,tokens,idx,NUM_DTYPES-1,symbol_type,r_errmess);
      }

      if ( *r_errmess == NULL && !eq_token(tokens,*idx,"else") )
        *r_errmess = mk_printf("Missing else clause in if-then-else");

      if ( *r_errmess == NULL )
      {
	*idx += 1;
	else_dx = 
	  mk_parse_part(ds,tokens,idx,NUM_DTYPES-1,symbol_type,r_errmess);
      }

      if ( *r_errmess == NULL && 
	   then_dx -> symbol_type != else_dx -> symbol_type )
        *r_errmess = mk_printf("Type mismatch in if-then-else: left %s, "
			       "right %s",
			       symbol_type_to_name(ds,then_dx->symbol_type),
			       symbol_type_to_name(ds,else_dx->symbol_type));

      if ( *r_errmess == NULL )
        dx = cons_trinary_dex(dx,then_dx,else_dx,IFTHENELSE);

      if ( *r_errmess != NULL && then_dx != NULL )
	free_dex(then_dx);
      if ( *r_errmess != NULL && else_dx != NULL )
	free_dex(else_dx);
    }
    else if ( dtype == REALVAL && is_a_number(token) )
    {
      if ( symbol_type >= 0 )
        *r_errmess = mk_printf("Type mismatch: I wanted a value of attribute "
                               "%s, not a real number",
                               ds_attnum_to_name(ds,symbol_type));
      else
      {
        dx = mk_realval_dex(atof(token));
        *idx += 1;
      }
    }
    else if ( (dtype == ROW || dtype == MISSING || dtype == RANDOM || dtype == GAUSSIAN ) && 
              eq_string(token,dtype_to_name(dtype)) )
    {
      dx = mk_empty_dex(dtype);
      *idx += 1;
    }
    else if ( dtype == SYMBOLVAL && 
              symbol_type >= 0 &&
              ds_attnum_name_to_value(ds,symbol_type,token) >= 0 )
    {
      int value = ds_attnum_name_to_value(ds,symbol_type,token);
      dx = mk_symbolval_dex(ds,symbol_type,value);
      *idx += 1;
    }
    else if ( dtype == ATTNUM && eq_token(tokens,*idx,"(") )
    {
      *idx += 1;
      dx = mk_parse_part(ds,tokens,idx,NUM_DTYPES-1,symbol_type,r_errmess);
      if ( *r_errmess == NULL && eq_token(tokens,*idx,")") )
        *idx += 1;
      else if ( *r_errmess == NULL )
        *r_errmess = mk_printf("Missing closing parenthesis");
    }
    else if ( dtype == ATTNUM )
    {
      char *name = string_array_ref(tokens,*idx);
      int attnum = ds_name_to_attnum(ds,name);

      if ( attnum < 0 && symbol_type >= 0 )
        *r_errmess = mk_printf("There is no attnum called %s and %s has no "
                               "value called %s",name,
                               ds_attnum_to_name(ds,symbol_type),name);
      else if ( attnum < 0 )
        *r_errmess = mk_printf("There is no attnum called %s",name);
        /* The following lines commented back in by AWM... Artur thinks they should
           be commented out. */
      /*
      else if ( symbol_type >= 0 && attnum != symbol_type )
      {
        printf("WARNING: The error message you are about to receive IS an\n"
               "         error according to AWM but should be commented out\n"
               "         according to AWD. TODO: Fix this\n");
        *r_errmess = mk_printf("Type mismatch: I needed a value of attribute "
                               "%s, not %s",ds_attnum_to_name(ds,symbol_type),
                               ds_attnum_to_name(ds,attnum));
      }
      */
      else
      {
        dx = mk_attnum_dex(ds,attnum);
        *idx += 1;
      }
    }
    else if ( dtype == NOT )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(NOT)) )
      {
        *idx += 1;
        dx = mk_parse_part(ds,tokens,idx,NOT,symbol_type,r_errmess);
        dx = cons_unary_dex(dx,NOT);
      }
      else
        dx = mk_parse_part(ds,tokens,idx,NOT-1,symbol_type,r_errmess);
    }
    else if (dtype == POWER)
    {
      if (eq_token(tokens,*idx,dtype_to_name(POWER)))
      {
	dex *dy = NULL;
	*idx += 1;
	dy = mk_parse_part(ds,tokens,idx,POWER,symbol_type,r_errmess);
	if (*r_errmess == NULL && dy->symbol_type >= 0)
	  *r_errmess = mk_printf("sqrt can't process a symbolic attribute, %s, for the first argument",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  dx = mk_parse_part(ds,tokens,idx,POWER,symbol_type,r_errmess);
	  if (*r_errmess == NULL && dx->symbol_type >= 0)
	    *r_errmess = mk_printf("sqrt can't process a symbolic attribute, %s, for the second argument",
				   ds_attnum_to_name(ds,dx->symbol_type));
	  else
	    dx = cons_binary_dex(dy,dx,POWER);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,POWER-1,symbol_type,r_errmess);
    }
    else if ( dtype == ABS )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(ABS) ) )
      {
		  *idx += 1;
		  dx = mk_parse_part(ds,tokens,idx,ABS,symbol_type,r_errmess);
		  if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
			  *r_errmess = mk_printf("abs can't process symbolic attribute %s",
			  ds_attnum_to_name(ds,dx->symbol_type));
		  else
			  dx = cons_unary_dex(dx,ABS);
	  }
      else
        dx = mk_parse_part(ds,tokens,idx,ABS-1,symbol_type,r_errmess);
    }
   else if ( dtype == SQRT )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(SQRT) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,SQRT,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("sqrt can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  if( *r_errmess == NULL && dx->attnum >= 0 && ds_att_min(ds,dx->attnum) < 0.0) 
	  {
	    *r_errmess = mk_printf("sqrt can't process attribute %s which assumes negative value(s)",
				   ds_attnum_to_name(ds,dx->attnum));
	  }
	  else
	  {
	    /*
	      if( *r_errmess == NULL && dx->attnum < 0 && dx->realval < 0.0 )

	      print_dex(ds,dx);  DEBUGGING ONLY, by Artur
	      printf("symbol type = %d\n",dx->symbol_type);
	      printf("dtype = %d\n",dx->dtype);
	      printf("attnum = %d\n",dx->attnum);
	      printf("realval = %g\n",dx->realval);
	      printf("symbolval = %d\n",dx->symbolval);
	      print_dex(ds,dx->arg1);  DEBUGGING ONLY, by Artur
	      printf("symbol type = %d\n",dx->arg1->symbol_type);
	      printf("dtype = %d\n",dx->arg1->dtype);
	      printf("attnum = %d\n",dx->arg1->attnum);
	      printf("realval = %g\n",dx->arg1->realval);
	      printf("symbolval = %d\n",dx->arg1->symbolval);
	      *r_errmess = mk_printf("sqrt can't process negative values:
	      %g",dx->realval);

	      dx = cons_unary_dex(dx,SQRT);
	      }
	      else
	    */
					  dx = cons_unary_dex(dx,SQRT);
			  }
		  }
	  }
      else
        dx = mk_parse_part(ds,tokens,idx,SQRT-1,symbol_type,r_errmess);
    }
   else if ( dtype == SIGN )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(SIGN) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,SIGN,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("sign can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  dx = cons_unary_dex(dx,SIGN);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,SIGN-1,symbol_type,r_errmess);
    }
   else if ( dtype == EXP )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(EXP) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,EXP,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("exp can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  dx = cons_unary_dex(dx,EXP);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,EXP-1,symbol_type,r_errmess);
    }
    else if ( dtype == LOG )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(LOG) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,LOG,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("log can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  if( *r_errmess == NULL && dx->attnum >= 0 &&
	      ds_att_min(ds,dx->attnum) <= 0.0) 
	    *r_errmess = mk_printf("log can process attribute %s which assumes"
				   " only positive value(s)",
				   ds_attnum_to_name(ds,dx->attnum));
	  else
	    /* if( *r_errmess == NULL && dx->attnum < 0 && dx->realval <= 0.0 )
	     *r_errmess = mk_printf("log can process only positive values:
	     %g",dx->realval);
	     else */
	    dx = cons_unary_dex(dx,LOG);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,LOG-1,symbol_type,r_errmess);
    }
    else if ( dtype == NEGP )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(NEGP) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,NEGP,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("negp can't process symbolic attribute %s", ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  dx = cons_unary_dex(dx,NEGP);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,NEGP-1,symbol_type,r_errmess);
    }
    else if ( dtype == RECP )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(RECP) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,RECP,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("recp can't process symbolic attribute %s", ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  dx = cons_unary_dex(dx,RECP);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,RECP-1,symbol_type,r_errmess);
    }
    else if ( dtype == LOGP )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(LOGP) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,LOGP,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("logp can't process symbolic attribute %s", ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  dx = cons_unary_dex(dx,LOGP);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,LOGP-1,symbol_type,r_errmess);
    }
    else if ( dtype == LOG10 )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(LOG10) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,LOG10,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("log10 can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	{
	  if( *r_errmess == NULL && dx->attnum >= 0 && ds_att_min(ds,dx->attnum) <= 0.0) 
	    *r_errmess = mk_printf("log10 can process attribute %s which "
				   "assumes only positive value(s)",
				   ds_attnum_to_name(ds,dx->attnum));
	  else
	     dx = cons_unary_dex(dx,LOG10);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,LOG10-1,symbol_type,r_errmess);
    }

    else if (dtype == SIN)
    {
      if (eq_token(tokens,*idx,dtype_to_name(SIN)))
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,SIN,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("sin can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	  dx = cons_unary_dex(dx,SIN);
      }
      else
        dx = mk_parse_part(ds,tokens,idx,SIN-1,symbol_type,r_errmess);
    }

    else if (dtype == COS)
    {
      if (eq_token(tokens,*idx,dtype_to_name(COS)))
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,COS,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> symbol_type >= 0 )
	  *r_errmess = mk_printf("cos can't process symbolic attribute %s",
				 ds_attnum_to_name(ds,dx->symbol_type));
	else
	  dx = cons_unary_dex(dx,COS);
      }
      else
        dx = mk_parse_part(ds,tokens,idx,COS-1,symbol_type,r_errmess);
    }

    /* Jeff takes a wild guess at what should happen here.
       He wonders how you could have dtype matching YEAR, but the second
       test, whatever it is, failing.  It must be part of this strange
       tail-recursion search for the right dtype
     */
    else if ( dtype == YEAR )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(YEAR) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,YEAR,symbol_type,r_errmess);
	if ((*r_errmess == NULL) && (dx->symbol_type < 0))
	  *r_errmess = mk_printf("year can process only symbolic values");
	else
	  dx = cons_unary_dex(dx,YEAR);
      }
      else
	dx = mk_parse_part(ds,tokens,idx,YEAR-1,symbol_type,r_errmess);
    }
    else if ( dtype == DAYS )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(DAYS) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,DAYS,symbol_type,r_errmess);
	if ((*r_errmess == NULL) && (dx->symbol_type < 0))
	  *r_errmess = mk_printf("days can process only symbolic values");
	else
	  dx = cons_unary_dex(dx,DAYS);
      }
      else
	dx = mk_parse_part(ds,tokens,idx,DAYS-1,symbol_type,r_errmess);
    }
    else if ( dtype == YEARDAYS )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(YEARDAYS) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,YEARDAYS,symbol_type,r_errmess);
	if ((*r_errmess == NULL) && (dx->symbol_type < 0))
	  *r_errmess = mk_printf("yeardays can process only symbolic values");
	else
	  dx = cons_unary_dex(dx,YEARDAYS);
      }
      else
	dx = mk_parse_part(ds,tokens,idx,YEARDAYS-1,symbol_type,r_errmess);
    }
    else if ( dtype == DAYFRAC )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(DAYFRAC) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,DAYFRAC,symbol_type,r_errmess);
	if ((*r_errmess == NULL) && (dx->symbol_type < 0))
	  *r_errmess = mk_printf("dayfrac can process only symbolic values");
	else
	  dx = cons_unary_dex(dx,DAYFRAC);
      }
      else
	dx = mk_parse_part(ds,tokens,idx,DAYFRAC-1,symbol_type,r_errmess);
    }
    else if ( dtype == ROUND )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(ROUND) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,ROUND,symbol_type,r_errmess);
	if ((*r_errmess == NULL) && (dx->symbol_type >= 0))
	  *r_errmess = mk_printf("dayfrac can not process symbolic values");
	else
	  dx = cons_unary_dex(dx,ROUND);
      }
      else
	dx = mk_parse_part(ds,tokens,idx,ROUND-1,symbol_type,r_errmess);
    }
    else if ( dtype == PRE )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(PRE) ) )
      {
	*idx += 1;
	dx = mk_parse_part(ds,tokens,idx,PRE,symbol_type,r_errmess);
	if ( *r_errmess == NULL && dx -> attnum < 0 )
	  *r_errmess = mk_printf("pre is applicable only to data attributes, not to individual values");
	else
	{
	  dx = cons_unary_dex(dx,PRE);
	}
      }
      else
        dx = mk_parse_part(ds,tokens,idx,PRE-1,symbol_type,r_errmess);
    }
  else if ( dtype == SUC )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(SUC) ) )
      {
		  *idx += 1;
		  dx = mk_parse_part(ds,tokens,idx,SUC,symbol_type,r_errmess);
		  if ( *r_errmess == NULL && dx -> attnum < 0 )
			  *r_errmess = mk_printf("suc is applicable only to data attributes, not to individual values");
		  else
		  {
				  dx = cons_unary_dex(dx,SUC);
		  }
	  }
      else
        dx = mk_parse_part(ds,tokens,idx,SUC-1,symbol_type,r_errmess);
    }
 else if ( dtype == DERIV )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(DERIV) ) )
      {
		  dex *dy = NULL;
		  *idx += 1;
		  dy = mk_parse_part(ds,tokens,idx,DERIV,symbol_type,
				     r_errmess);
		  if ( *r_errmess == NULL &&
		       ( dy -> attnum < 0 || dy->symbol_type >= 0 ) )
		    *r_errmess = mk_printf("deriv is applicable only to real valued attributes");
		  else
		  {
		    /* *idx += 1; */
		    dx = mk_parse_part(ds,tokens,idx,DERIV,symbol_type,r_errmess);
		    if ( *r_errmess == NULL && ( dx -> attnum < 0 || dx->symbol_type >= 0 ) )
				  *r_errmess = mk_printf("deriv is applicable only to real valued attributes");
			  else
				  dx = cons_binary_dex(dy,dx,DERIV);
		  }
	  }
      else
        dx = mk_parse_part(ds,tokens,idx,DERIV-1,symbol_type,r_errmess);
    }
   else if ( dtype == RANKS )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(RANKS) ) )
      {
		  *idx += 1;
		  dx = mk_parse_part(ds,tokens,idx,RANKS,symbol_type,r_errmess);
		  /*
		    now we accept symbolic atts
		    if ( *r_errmess == NULL && (dx->attnum<0 || dx->symbol_type>=0) )
		    *r_errmess = mk_printf("rank can process only real valued attributes so far",
		    ds_attnum_to_name(ds,dx->symbol_type));
		    else
		    {
		  */
		  dx = cons_unary_dex(dx,RANKS);
		  /* } */
      }
      else
        dx = mk_parse_part(ds,tokens,idx,RANKS-1,symbol_type,r_errmess);
    }
  else if ( dtype == SRANKS )
    {
      if ( eq_token(tokens,*idx,dtype_to_name(SRANKS) ) )
      {
		  dex *dy = NULL;
		  *idx += 1;
		  dy = mk_parse_part(ds,tokens,idx,SRANKS,symbol_type,
				     r_errmess);
		  if ( *r_errmess == NULL &&
		       ( dy -> attnum < 0 || dy->symbol_type < 0 ) )
		    *r_errmess = mk_printf("sranks expects its first argument to be symbolic");
		  else
		  {
		    dx = mk_parse_part(ds,tokens,idx,SRANKS,symbol_type,
				       r_errmess);
		    if ( *r_errmess == NULL &&
			 ( dx -> attnum < 0 || dx->symbol_type >= 0 ) )
		      *r_errmess = mk_printf("sranks expects its second argument to be real");
		    else
		      dx = cons_binary_dex(dy,dx,SRANKS);
		  }
      }
      else
        dx = mk_parse_part(ds,tokens,idx,SRANKS-1,symbol_type,r_errmess);
    }
  
    else
    {
      dx = mk_parse_part(ds,tokens,idx,dtype-1,symbol_type,r_errmess);

      if ( *r_errmess == NULL && eq_token(tokens,*idx,dtype_to_name(dtype)) )
      {
        if ( dtype == EQ || dtype == NEQ )
        {
          dex *arg2;
          *idx += 1;
          arg2=mk_parse_part(ds,tokens,idx,dtype,dx->symbol_type,r_errmess);
          if ( *r_errmess == NULL )
          {
            if ( dx->symbol_type != arg2->symbol_type )
            {
              *r_errmess = mk_printf("Type mismatch in arguments of %s:"
                                     "lefthand is %s, righthand is %s",
                                     dtype_to_name(dtype),
                                     symbol_type_to_name(ds,dx->symbol_type),
                                    symbol_type_to_name(ds,arg2->symbol_type));
              free_dex(arg2);
            }
            else
              dx = cons_binary_dex(dx,arg2,dtype);
          }
        }
        else if ( dx -> symbol_type >= 0 )
          *r_errmess = mk_printf("Attempted to use the symbolic attribute %s "
                                 "in a numeric expression",
                                 ds_attnum_to_name(ds,dx->symbol_type));
        else
        {
          dex *arg2;
          *idx += 1;
          arg2 = mk_parse_part(ds,tokens,idx,dtype,symbol_type,r_errmess);
          if ( *r_errmess == NULL )
          {
            if ( arg2 -> symbol_type >= 0 )
            {
              *r_errmess = mk_printf("Attempted to use the symbolic attribute "
                                     "%s in a numeric expression",
                                     ds_attnum_to_name(ds,arg2->symbol_type));
              free_dex(arg2);
            }
            else
              dx = cons_binary_dex(dx,arg2,dtype);
          }
        }
      }
    }

    if ( *r_errmess != NULL )
    {
      if ( dx != NULL ) free_dex(dx);
      dx = NULL;
    }

    if ( *r_errmess == NULL && dx == NULL ) my_error("Parser bust");
  }
  return dx;
}


/* attnum refers to the type of the thing we want to return. -1
   means "I just want a numeric type back" */
dex *mk_parse_dex(datset *ds,int attnum,string_array *tokens,char **r_errmess)
{
  int idx = 0;
  dex *dx;
  int top_dtype =  NUM_DTYPES - 1;
  int symbol_type = (attnum < 0) ? -1 :
                    (ds_attnum_to_symbolicp(ds,attnum)) ? attnum : -1;
  *r_errmess = NULL;

  dx = mk_parse_part(ds,tokens,&idx,top_dtype,symbol_type,r_errmess);

  if ( dx != NULL && idx != string_array_size(tokens) )
  {
    *r_errmess=mk_printf("Unknown operator %s",string_array_ref(tokens,idx));
    free_dex(dx);
    dx = NULL;
  }

  return dx;
}

void add_to_string_array_start(char *s,string_array *sa)
{
  int i;
  add_to_string_array(sa,"dummy");
  for ( i = string_array_size(sa)-1 ; i > 0 ; i-- )
    string_array_set(sa,i,string_array_ref(sa,i-1));
  string_array_set(sa,0,s);
}

void parenthesize_sa(string_array *sa)
{
  add_to_string_array_start("(",sa);
  add_to_string_array(sa,")");
}

string_array *mk_append_string_arrays(string_array *a,string_array *b)
{
  string_array *sa = mk_copy_string_array(a);
  int i;
  for ( i = 0 ; i < string_array_size(b) ; i++ )
    add_to_string_array(sa,string_array_ref(b,i));
  return sa;
}

string_array *mk_string_array_from_dex(datset *ds,dex *dx)
{
  int dtype = dex_dtype(dx);
  string_array *sa;

  if ( dtype == ATTNUM )
    sa = mk_string_array_1(ds_attnum_to_name(ds,dex_attnum(dx)));
  else if ( dtype == ROW || dtype == MISSING || dtype == RANDOM || dtype == GAUSSIAN )
    sa = mk_string_array_1(dtype_to_name(dtype));
  else if ( dtype == REALVAL )
  {
    char buff[100];
    sprintf(buff,"%g",dex_realval(dx));
    sa = mk_string_array_1(buff);
  }
  else if ( dtype == SYMBOLVAL )
    sa = mk_string_array_1(ds_attnum_value_to_name(ds,dx->symbol_type,
                                                   dx->symbolval));
  else if ( dtype == IFTHENELSE )
  {
    string_array *sa1 = mk_string_array_from_dex(ds,dex_arg1(dx));
    string_array *sa2 = mk_string_array_from_dex(ds,dex_arg2(dx));
    string_array *sa3 = mk_string_array_from_dex(ds,dex_arg3(dx));
    string_array *sa12;
    add_to_string_array_start("if",sa1);
    add_to_string_array(sa1,"then");
    add_to_string_array(sa2,"else");
    sa12 = mk_append_string_arrays(sa1,sa2);
    sa = mk_append_string_arrays(sa12,sa3);
    free_string_array(sa1);
    free_string_array(sa2);
    free_string_array(sa3);
    free_string_array(sa12);
  }
  else if ( dtype == NOT )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > NOT )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(NOT),sa);
  }
   else if ( dtype == ABS )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > ABS )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(ABS),sa);
  }
    else if ( dtype == SQRT )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > SQRT )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(SQRT),sa);
  }
     else if ( dtype == SIGN )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > SIGN )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(SIGN),sa);
  }
     else if ( dtype == EXP )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > EXP )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(EXP),sa);
  }
     else if ( dtype == LOG )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > LOG )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(LOG),sa);
  }
     else if ( dtype == EXPP )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > EXPP )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(EXPP),sa);
  }

     else if ( dtype == LOGP )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > LOGP )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(LOGP),sa);
  }
     else if ( dtype == NEGP )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > NEGP )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(NEGP),sa);
  }
     else if ( dtype == RECP )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > RECP )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(RECP),sa);
  }
  else if ( dtype == LOG10 )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > LOG10 )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(LOG10),sa);
  }
  /* I wonder why these all test for greater than themselves to parenthesize
     JGS 11-25-02 */
  else if ( dtype == SIN )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > SIN )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(SIN),sa);
  }
  else if ( dtype == COS )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > COS )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(COS),sa);
  }
  else if ( dtype == YEAR )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > YEAR ) parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(YEAR),sa);
  }
  else if ( dtype == YEARDAYS )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > YEARDAYS ) parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(YEARDAYS),sa);
  }
  else if ( dtype == DAYS )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > DAYS ) parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(DAYS),sa);
  }
  else if ( dtype == DAYFRAC )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > DAYFRAC ) parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(DAYFRAC),sa);
  }
  else if ( dtype == ROUND )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > ROUND ) parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(ROUND),sa);
  }
  else if ( dtype == PRE )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > PRE )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(PRE),sa);
  }
     else if ( dtype == SUC )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > SUC )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(SUC),sa);
  }
     else if ( dtype == RANKS )
  {
    sa = mk_string_array_from_dex(ds,dex_arg1(dx));
    if ( dex_dtype(dex_arg1(dx)) > RANKS )
      parenthesize_sa(sa);
    add_to_string_array_start(dtype_to_name(RANKS),sa);
  }
  else
  {
    string_array *sa1 = mk_string_array_from_dex(ds,dex_arg1(dx));
    string_array *sa2 = mk_string_array_from_dex(ds,dex_arg2(dx));

    if ( dex_dtype(dex_arg1(dx)) > dex_dtype(dx) )
      parenthesize_sa(sa1);
    
    if ( dex_dtype(dex_arg2(dx)) > dex_dtype(dx) )
      parenthesize_sa(sa2);
    
	if( dtype == DERIV || dtype == SRANKS )
	{
		add_to_string_array_start(dtype_to_name(dtype),sa1);
	}
	else
	{
		add_to_string_array(sa1,dtype_to_name(dex_dtype(dx)));
	}
	sa = mk_append_string_arrays(sa1,sa2);

	free_string_array(sa1);
    free_string_array(sa2);
  }
  
  return sa;
}

char *mk_string_from_dex(datset *ds,dex *dx)
{
  string_array *sa = mk_string_array_from_dex(ds,dx);
  char *s = mk_string_from_string_array(sa);
  free_string_array(sa);
  return s;
}

void fprint_dex(FILE *s,datset *ds,dex *dx)
{
  char *string = mk_string_from_dex(ds,dx);
  fprintf(s,"%s\n",string);
  free_string(string);
}

void print_dex(datset *ds,dex *dx)
{
  fprint_dex(stdout,ds,dx);
}

#define l2d(x) ((x)?1.0:0.0)
#define d2l(x) ((x)!=0.0)

double safe_divide(double v1,double v2,bool *r_missing)
{
  double result = -7777.7;

  *r_missing = FALSE;

  if ( v2 == 0.0 )
    *r_missing = TRUE;
  else if ( fabs(v2) > 1e-20 && fabs(v1) < 1e20 )
    result = v1 / v2;
  else if ( fabs(v1) < 1.0 )
    result = v1 / v2;
  else
  {
    double log_abs_v1 = log10(fabs(v1));
    double log_abs_v2 = log10(fabs(v2));
    double log_double_max = 100.0;

    if ( log_abs_v1 - log_abs_v2 > log_double_max * 0.99 )
      *r_missing = TRUE;
    else
      result = v1 / v2;
  }

  return result;
}

/* a bunch of random time/date functions */
/* this should produce the right answer for any string that ends with the
   year and uses some non-numeric separator to distinguish year from the
   rest of the string.  It does not attempt to force 2 digit or 4 digit
   years.  What you give it is what you get from it.

   For example  "March-01-1999"  => 1999
                "March-01-99"    => 99
		"3/1/99"         => 99
		"I_don't_know99" => 99
   
   MOD: The function now also checks whether it can read a number straight
        from the front of the string.  If that number happens to look like
        a 4 digit year, it uses that number instead.  JGS  12-11-03
*/
double year_from_date_string(char *s)
{
  double result = -1.0;
  int last_non_numeric = 0;
  int i;
  bool ok = FALSE;
  int front_year;

  sscanf(s,"%d",&front_year);
  for (i=0;i<(int)strlen(s);i++)
    if ((s[i] < '0') || (s[i] > '9')) last_non_numeric = i;

  result = double_from_string(s+last_non_numeric+1,&ok);

  if ((front_year > 999) && (front_year < 3000)) return (double)front_year;
  return result;
}

void clear_tm(struct tm *a)
{
  a->tm_sec = 0;  /* 0-59 */
  a->tm_min = 0;  /* 0-59 */
  a->tm_hour = 0; /* 0-23 */
  a->tm_mday = 1; /* 1-31 */
  a->tm_mon = 0;  /* 0-11 */
  a->tm_year = 0; /* year-1900 */
  a->tm_wday = 0; /* 0-6 */
  a->tm_yday = 0; /* 0-365 */
  a->tm_isdst = 0;/* boolean daylight savings time */
}

/* This function is not really used, its just to illustrate one way
   to do this.  Feel free to edit it and use it.
*/
int tm_time_diff(struct tm *a, struct tm *b)
{
  time_t att = mktime(a);
  time_t btt = mktime(b);

  double secs = difftime(att,btt);

  printf("%.2f seconds, %.2f days\n",secs,secs/(24.0*60.0*60.0));

  return (int)secs;
}

/* The following two functions use a probably unreliable hack.  The tm
   struct is filled in with the appropriate year/month/day with everything
   else zeroed out.  Then its converted to and back from a time_t struct.
   In the conversion back, the rest of the tm struct is filled in 
   appropriately including the tm_yday field, which is then returned.
*/
int tm_year_days(struct tm *a)
{
  time_t att = mktime(a);
  struct tm *b = gmtime(&att);
  return b->tm_yday;
}

/* return the number of days this year, jan1 = 0.
   year must be its full name, month counts from 1, and day counts from 1
*/
double yeardays(int year, int month, int day)
{
  double result = 0.0;
  struct tm *a = (struct tm *)am_malloc(sizeof(struct tm));
  clear_tm(a);
  a->tm_year = year - 1900;
  a->tm_mon = month - 1;
  a->tm_mday = day;
  result = tm_year_days(a);
  am_free((void *)a,sizeof(struct tm));
  return result;
}

/* return the number of days since 1900. jan1, 1900 = 0.
   year must be its full name, month counts from 1, and day counts from 1
*/
double days_fn(int year, int month, int day)
{
  int num_years = year - 1900;
  int num_previous_leap_years = (num_years + 3)/4;

  num_previous_leap_years -= (num_years-1)/100;

  num_previous_leap_years += (num_years < 100) ? 0 : ((num_years+299)/400);

  return ((double) num_years) * 365 + num_previous_leap_years + 
	  yeardays(year,month,day);
}

/* This function is very fragile.  It relies on undocumented properties
   of the standard time library.  Therefore, I expect this to break at
   some random future time.  Someone please substitute this with another
   method.

   From a date string, this function should return the number of days this
   year with Jan 1 being 0 and Dec 31 of a non-leap year being 364.

   The am in the name is for "american."  This function only works with
   numbered months.  It does not recognize any month names.  Aside from
   that, any separator is fine.

   This function makes Y2K guesses.  If you give it a 2-digit year, it
   will make a guess at what you mean (1920-2019).  If the year is blank,
   it will take a guess that you mean a non-leap year.

   For example  "March-01-1999"  => garbage
                "03-01"          => 59
		"3/1/1999"       => 59
		"2don't1know99"  => 31

   The american argument is a boolean indicating american date format 
   (month-day-year).  If it is false, it assumes (day-month-year) format

   MOD:  It now checks the first number read and if that number happens
         to look like a 4 digit year, then it assumes the string is in
         year-month-day format  -- JGS  12-11-03
*/
double yeardays_from_date_string(char *s, bool american)
{
  double result = -1.0;
  int i;
  int state = 0; /* 0 - previous character is non-numeric */
                 /* 1 - previous character is numeric     */
                 /* 2 - bailout                           */
  int parts[3] = {0,0,-1};
  int partcount = 0;
  bool yearfirst = FALSE;

  for (i=0;(i<(int)strlen(s))&&(state!=2);i++)
  {
    switch(state)
    {
      case 0:
      {
	if ((s[i] < '0') || (s[i] > '9')) {}
	else
	{
	  state = 1;
	  sscanf(s+i,"%d",&(parts[partcount]));
	  partcount++;
	  if (partcount > 2) state = 2;
	}
	break;
      }
      case 1:
      {
	if ((s[i] < '0') || (s[i] > '9'))
	{
	  state = 0;
	}
	else {}
	break;
      }
      default:
	my_error("yeardays_from_date_string: bad state");
    }
  }

  if (parts[0] > 31) yearfirst = TRUE;
  else
  {
    if (parts[2] < 0) parts[2] = 1999;
    else if (parts[2] < 20) parts[2] += 2000;
    else if (parts[2] < 100) parts[2] += 1900;
  }
  
  if (yearfirst)
  {
    result = yeardays(parts[0],parts[1],parts[2]);
  }
  else
  {
    if (american) result = yeardays(parts[2],parts[0],parts[1]);
    else          result = yeardays(parts[2],parts[1],parts[0]);
  }

  return result;
}

/* This function is very fragile.  It relies on undocumented properties
   of the standard time library.  Therefore, I expect this to break at
   some random future time.  Someone please substitute this with another
   method.

   From a date string, this function should return the number of days this
   year with Jan 1 being 0 and Dec 31 of a non-leap year being 364.

   The am in the name is for "american."  This function only works with
   numbered months.  It does not recognize any month names.  Aside from
   that, any separator is fine.

   This function makes Y2K guesses.  If you give it a 2-digit year, it
   will make a guess at what you mean (1920-2019).  If the year is blank,
   it will take a guess that you mean a non-leap year.

   For example  "March-01-1999"  => garbage
                "03-01"          => 59
		"3/1/1999"       => 59
		"2don't1know99"  => 31

   The american argument is a boolean indicating american date format 
   (month-day-year).  If it is false, it assumes (day-month-year) format

   MOD:  It now checks the first number read and if that number happens
         to look like a 4 digit year, then it assumes the string is in
         year-month-day format  -- JGS  12-11-03
*/
double days_from_date_string(char *s, bool american)
{
  double result = -1.0;
  int i;
  int state = 0; /* 0 - previous character is non-numeric */
                 /* 1 - previous character is numeric     */
                 /* 2 - bailout                           */
  int parts[3] = {0,0,-1};
  int partcount = 0;
  bool yearfirst = FALSE;

  for (i=0;(i<(int)strlen(s))&&(state!=2);i++)
  {
    switch(state)
    {
      case 0:
      {
	if ((s[i] < '0') || (s[i] > '9')) {}
	else
	{
	  state = 1;
	  sscanf(s+i,"%d",&(parts[partcount]));
	  partcount++;
	  if (partcount > 2) state = 2;
	}
	break;
      }
      case 1:
      {
	if ((s[i] < '0') || (s[i] > '9'))
	{
	  state = 0;
	}
	else {}
	break;
      }
      default:
	my_error("yeardays_from_date_string: bad state");
    }
  }

  if (parts[0] > 31) yearfirst = TRUE;
  else
  {
    if (parts[2] < 0) parts[2] = 1999;
    else if (parts[2] < 20) parts[2] += 2000;
    else if (parts[2] < 100) parts[2] += 1900;
  }
  
  if (yearfirst)
  {
    result = yeardays(parts[0],parts[1],parts[2]);
  }
  else
  {
    if (american) result = yeardays(parts[2],parts[0],parts[1]);
    else          result = yeardays(parts[2],parts[1],parts[0]);
  }

  return result;
}

/* This returns the fraction of the day indicated by this time.  It should
   work with any string using a non-numeric separator for hours, minutes,
   and seconds.  It accepts strings that have all three, or just hours and
   minutes.  It assumes hours are in 24 hour format.

   For example  "12:00"           => 0.5000
                "12/30"           => 0.5208
                "12.30.30"        => 0.5212
                "3:00 PM"         => 0.1250 (assumes its 3 am)
                "3garbage4stuff8" => whatever 3:4:8 is
*/
double dayfrac_from_time_string(char *s)
{
  double result = -1.0;
  int i;
  int state = 0; /* 0 - previous character is non-numeric */
                 /* 1 - previous character is numeric     */
                 /* 2 - bailout                           */
  int parts[3] = {0,0,0};
  int partcount = 0;

  for (i=0;(i<(int)strlen(s))&&(state!=2);i++)
  {
    switch(state)
    {
      case 0:
      {
	if ((s[i] < '0') || (s[i] > '9')) {}
	else
	{
	  state = 1;
	  sscanf(s+i,"%d",&(parts[partcount]));
	  partcount++;
	  if (partcount > 2) state = 2;
	}
	break;
      }
      case 1:
      {
	if ((s[i] < '0') || (s[i] > '9'))
	{
	  state = 0;
	}
	else {}
	break;
      }
      default:
	my_error("dayfrac_from_time_string: bad state");
    }
  }

  result = (double)parts[0] / 24.0 +
           (double)parts[1] / (24.0*60.0) +
           (double)parts[2] / (24.0*60.0*60.0);

  return result;
}

/* Jeff attempts to hack in some data/time functions.  They're a misfit
   because they have string inputs, etc., but some of them might work.
   JGS 3-28-01
*/
double eval_dex(datset *ds,dex *dx,int row,bool *r_missing)
{
  double result = -77.77;
  *r_missing = FALSE;

  switch ( dx->dtype )
  {
    case ATTNUM: 
    {
      if ( ds_value_is_missing(ds,row,dx->attnum) )
      {
	result = 0.0;
	*r_missing = TRUE;
      }
      else if ( ds_attnum_to_symbolicp(ds,dx->attnum) )
        result = ds_symbol_ref(ds,row,dx->attnum);
      else
        result = ds_real_ref(ds,row,dx->attnum);
      break;
    }
    case ROW: result = (double) row; break;
    case MISSING: result = ds_row_to_num_missing(ds,row); break;
    case RANDOM: result = range_random(0.0,1.0); break;
    case GAUSSIAN: result = gen_gauss(); break;
    case REALVAL: result = dx->realval; break;
    case SYMBOLVAL: result = (double) dx->symbolval; break;
    case NOT:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result = (*r_missing) ? 0.0 : l2d(!d2l(v));
      break;
    }
    case ABS:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result = (*r_missing) ? 0.0 : fabs(v);
      break;
    }
    case SQRT:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : sqrt(v);
      break;
    }
    case SIGN:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : ( v>0.0 ? 1.0 : (v<0.0 ? -1.0 : 0.0) );
      break;
    }
    case LOG:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : log(v);
      break;
    }
    case LOG10:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : log10(v);
      break;
    }
    case SIN:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : sin(v);
      break;
    }
    case COS:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : cos(v);
      break;
    }
    case YEAR:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      int intv = my_irint(v);
      /* this is cheating, but eval_dex does not return the string name, or 
         the attnum, or anything else that will help us retrieve these.
	 its completely unclear whether we want the field attnum or the
	 field symbol_type, the following is just a guess.
       */
      int att = dx->arg1->symbol_type;
      char *s = ds_attnum_value_to_name(ds,att,intv);
      result = year_from_date_string(s);
      break;
    }
    case YEARDAYS:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      int intv = my_irint(v);
      /* this is cheating, but eval_dex does not return the string name, or 
         the attnum, or anything else that will help us retrieve these.
	 its completely unclear whether we want the field attnum or the
	 field symbol_type, the following is just a guess.
       */
      int att = dx->arg1->symbol_type;
      char *s = ds_attnum_value_to_name(ds,att,intv);
      result = yeardays_from_date_string(s,TRUE);
      break;
    }
    case DAYS:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      int intv = my_irint(v);
      /* this is cheating, but eval_dex does not return the string name, or 
         the attnum, or anything else that will help us retrieve these.
	 its completely unclear whether we want the field attnum or the
	 field symbol_type, the following is just a guess.
       */
      int att = dx->arg1->symbol_type;
      char *s = ds_attnum_value_to_name(ds,att,intv);
      result = days_from_date_string(s,TRUE);
      break;
    }
    case DAYFRAC:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      int intv = my_irint(v);
      /* this is cheating, but eval_dex does not return the string name, or 
         the attnum, or anything else that will help us retrieve these.
	 its completely unclear whether we want the field attnum or the
	 field symbol_type, the following is just a guess.
       */
      int att = dx->arg1->symbol_type;
      char *s = ds_attnum_value_to_name(ds,att,intv);
      result = dayfrac_from_time_string(s);
      break;
    }
    case ROUND:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result = (*r_missing) ? 0.0 : my_irint(v);
      break;
    }
    case LOGP:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : log(v*(e-1)+1);
      break;
    }
    case RECP:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : (2/(v+1) - 1);
      break;
    }   
    case NEGP:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : (-v + 1);
      break;
    }
    case EXP:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : exp(v);
      break;
    }
    case EXPP:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      result =  (*r_missing) ? 0.0 : (exp(v)-1)/(exp(1)-1);
      break;
    }
	case PRE:
    {
	  if( row > 0 )
		  result = eval_dex(ds,dx->arg1,row-1,r_missing);
	  else
		  result = eval_dex(ds,dx->arg1,row,r_missing);
      break;
    }
	case SUC:
    {
	  if( row < ds_num_rows(ds)-1 )
		  result = eval_dex(ds,dx->arg1,row+1,r_missing);
	  else
		  result = eval_dex(ds,dx->arg1,row,r_missing);
      break;
    }
    case DERIV:
    {
		static ivec *ind = NULL;
		static dyv *x = NULL;
		int l, r, i;
		bool signal = FALSE;
		double dlx, dly;
		int size = 0;

		if( ind == NULL )
		{
			x = mk_dyv_from_real_column(ds,dex_attnum(dx->arg2));
			ind = mk_indices_of_sorted_dyv(x);
			size = ivec_size(ind);
		}
		i = ivec_ref(ind,row);
		r = ivec_ref(ind, ( (i+1)<size ? i+1 : i ));
		l = ivec_ref(ind, ( i>0 ? i-1 : i ));
		while( fabs(dlx = dyv_ref(x,r) - dyv_ref(x,l))  < AWD_ZEROTOL )
		{
		  if( l==0 && r==(size-1) ) /* can't expand anymore */
				signal = TRUE;
			else
			{
			  if( l==0 ) /* can't expand towards left */
			    r++;
			  else 
			    {
			      if( r==(size-1) ) /* can't expand towards right*/
				l--;
			      else
				{
				  if( (i-l) > (r-i) )
				    r++;
				  else
				    l--;
				}
			    }
			}
		} /* while loop */
		i = dex_attnum(dx->arg1);
		dly = ds_real_ref(ds,r,i) - ds_real_ref(ds,l,i);
		if( signal ) /* XXX gcc will whine about this */
			result = (dly>0.0 ? 1.0 : (dly<0.0 ? -1.0 : 0.0)) * HUGE_VAL;
		else
			result = dly/dlx;
		
		if( (row == (ds_num_rows(ds)-1)) && ind != NULL )
		{
			free_ivec(ind);
			free_dyv(x);
			ind = NULL;
			x = NULL;
		}
		break;
	}
    case RANKS:
    {
		static dyv *rx = NULL;

		if( rx == NULL )
		{
			int attnum = dex_attnum(dx->arg1);
			dyv *x;
			if( ds_attnum_to_symbolicp(ds,attnum) )
			    x = mk_dyv_from_symbolic_col(ds,attnum);
			else
				x = mk_dyv_from_real_column(ds,attnum) ;				
			rx = mk_ranks_fast(x);
			free_dyv(x);
		}

		result = dyv_ref( rx, row );
		
		if( (row == (ds_num_rows(ds)-1)) && rx != NULL )
		{
			free_dyv(rx);
			rx = NULL;
		}
		break;
	}
    case SRANKS:
    {
		static dyv *rx = NULL;

		if( rx == NULL )
		{
			dyv *y = mk_dyv_from_real_column(ds,dex_attnum(dx->arg2));
			dyv *x = mk_dyv_from_symbolic_rows(ds, dex_attnum(dx->arg1), y, NULL );
			free_dyv(y);
			rx = mk_ranks_fast(x);
			free_dyv(x);
		}

		result = dyv_ref( rx, row );
		
		if( (row == (ds_num_rows(ds)-1)) && rx != NULL )
		{
			free_dyv(rx);
			rx = NULL;
		}
		break;
	}
    case IFTHENELSE:
    {
      double v = eval_dex(ds,dx->arg1,row,r_missing);
      if ( *r_missing )
	result = 0.0;
      else
        result = ( d2l(v) ) ? eval_dex(ds,dx->arg2,row,r_missing) : 
	                      eval_dex(ds,dx->arg3,row,r_missing);
      break;
    }
    default:
    {
      bool miss1,miss2;
      double v1 = eval_dex(ds,dx->arg1,row,&miss1);
      double v2 = eval_dex(ds,dx->arg2,row,&miss2);
      
      if ( miss1 || miss2 )
      {
	result = 0.0;
	*r_missing = TRUE;
      }
      else
      {
	switch ( dx->dtype )
        {
          case EQ: result = v1 == v2; break;
          case NEQ: result = v1 != v2; break;
          case DIVIDE: result = safe_divide(v1,v2,r_missing); break;
          case TIMES: result = v1 * v2; break;
	  case POWER: result = pow(v1,v2); break;
	  case MOD: result = (double)((int)v1 % (int)v2); break;
          case MINUS: result = v1 - v2; break;
          case PLUS: result = v1 + v2; break;
          case PLUSP: result = (v1 + v2)/2; break;
          case GEQ: result = v1 >= v2; break;
          case LEQ: result = v1 <= v2; break;
          case GREATER: result = v1 > v2; break;
          case LESSTHAN: result = v1 < v2; break;
          case AND: result = l2d( d2l(v1) && d2l(v2) ); break;
          case OR: result = l2d( d2l(v1) || d2l(v2) ); break;
          case IMPLIES: result = l2d( !d2l(v1) || d2l(v2) ); break;
          case IFF: result = l2d( d2l(v1) == d2l(v2) ); break;
          default: my_error("unknown dtype");
	}
      }
    }
  }
  return result;
}

void add_to_dex_attnums(dex *dx,ivec *attnums)
{
  if ( dx != NULL )
  {
    if ( dx->dtype == ATTNUM )
      add_to_ivec(attnums,dx->attnum);

    add_to_dex_attnums(dx->arg1,attnums);
    add_to_dex_attnums(dx->arg2,attnums);
    add_to_dex_attnums(dx->arg3,attnums);
  }
}

/* Returns sivec of all attnums appearing in dex */
ivec *mk_dex_attnums(dex *dx)
{
  ivec *attnums = mk_ivec(0);
  ivec *result;

  add_to_dex_attnums(dx,attnums);

  result = mk_sivec_from_ivec(attnums);

  free_ivec(attnums);

  return result;
}

void apply_dex_to_dset(datset *ds,int attnum,dex *dx)
{
  int row;
  for ( row = 0 ; row < ds_num_rows(ds) ; row++ )
  {
    bool missing;
    double value = eval_dex(ds,dx,row,&missing);

    if ( missing )
      ds_set_value_missing(ds,row,attnum);
    else if ( ds_attnum_to_symbolicp(ds,attnum) )
      ds_symbol_set(ds,row,attnum,(int)floor(0.5 + value));
    else
      ds_real_set(ds,row,attnum,value);
  }
}

