/* the world's simplest expression parser! 
 * intended for the find expressions -- anything complicated can be handled in some other way.
 * Rudimentary error checking, precedence taken from C.
 *
 * as of 11-Dec-97, shares symbols with the lisp-side in snd-clm.c (I need to make up my mind...)
 */

#define MAIN_PROGRAM_TEST 0

#if (MAIN_PROGRAM_TEST)
  #include <stdlib.h>
  #include <string.h>
  #include <stdio.h>
  #include <math.h>
  #include "snd-strings.h"
  typedef struct {int op; void *op0; void *op1; float fval; int type;} sop;
  float search_dot(int off) {return(1.0*off);}
  float search_dot_assign(int off,float val) {return(val);}
  float get_snd_var(int var) {return(var);}
  float set_snd_var(int var, float val) {return(val);}
  float sop_yn(int chan, int off) {return(0.0);}
#else
  #include "snd.h"
#endif

#include <setjmp.h>
static jmp_buf jmpbuf; /* for error exit */

#define SOP_OPS 56
#define MAX_STACK 32
#define MAX_ID_LEN 32

enum {sop_num,sop_plus,sop_minus,sop_times,sop_divide,sop_gt,sop_geq,sop_lt,sop_leq,sop_eq,sop_neq,sop_uminus,
      sop_open,sop_close,sop_dot,sop_log,sop_log10,sop_abs,sop_exp,sop_pow,sop_sqrt,sop_sin,sop_cos,sop_atan,
      sop_or,sop_and,sop_if,sop_colon,sop_acos,sop_asin,sop_cosh,sop_sinh,sop_tanh,sop_fmod,
      sop_ceil,sop_floor,sop_assign,sop_not,sop_plus_assign,sop_minus_assign,sop_times_assign,sop_divide_assign,
      sop_var,sop_func,sop_snd_var,sop_open_curly,sop_close_curly,
      sop_dot_1,sop_dot_2,sop_dot_3,sop_dot_4,sop_dot_5,sop_dot_6,sop_dot_7,sop_dot_8
};

static struct sop_data {
  int op; char *name; int precd; int args;
} all_sops[] = {
  {sop_num,"num",0,0}, {sop_plus,"+",11,2}, {sop_minus,"-",11,2}, {sop_times,"*",12,2}, {sop_divide,"/",12,2}, {sop_gt,">",8,2},
  {sop_geq,">=",8,2}, {sop_lt,"<",8,2}, {sop_leq,"<=",8,2}, {sop_eq,"==",7,2}, {sop_neq,"!=",7,2}, {sop_uminus,"u-",13,1},
  {sop_open,"(",0,1}, {sop_close,")",16,1}, {sop_dot,"y",14,0}, {sop_log,"log",14,1}, {sop_log10,"log10",14,1},
  {sop_abs,"abs",14,1}, {sop_exp,"exp",14,1}, {sop_pow,"pow",14,2}, {sop_sqrt,"sqrt",14,1}, {sop_sin,"sin",14,1},
  {sop_cos,"cos",14,1}, {sop_atan,"atan",14,2}, {sop_or,"||",2,2}, {sop_and,"&&",3,2},
  {sop_if,"?",0,2}, {sop_colon,":",2,2}, {sop_acos,"acos",14,1}, {sop_asin,"asin",14,1}, {sop_cosh,"cosh",14,1},
  {sop_sinh,"sinh",14,1}, {sop_tanh,"tanh",14,1}, {sop_fmod,"fmod",14,2}, {sop_ceil,"ceil",14,1}, {sop_floor,"floor",14,1},
  {sop_assign,"=",0,2},{sop_not,"!",15,1},{sop_plus_assign,"+=",0,2},{sop_minus_assign,"-=",0,2},
  {sop_times_assign,"*=",0,2},{sop_divide_assign,"/=",0,2},{sop_var,"var",0,0},{sop_func,"func",14,-1},{sop_snd_var,"snd_var",0,0},
  {sop_open_curly,"{",-1,1},{sop_close_curly,"}",16,1},
  {sop_dot_1,"y1",14,0},{sop_dot_2,"y2",14,0},{sop_dot_3,"y3",14,0},{sop_dot_4,"y4",14,0},
  {sop_dot_5,"y5",14,0},{sop_dot_6,"y6",14,0},{sop_dot_7,"y7",14,0},{sop_dot_8,"y8",14,0}
};

static int sop_precd(int op) {return(all_sops[op].precd);}
static int sop_args(int op) {return(all_sops[op].args);}
static char *sop_op_name(int op) {return(all_sops[op].name);}

static int sop_parse_error = 0;
enum {sop_no_error,sop_too_many_open_parens,sop_not_enough_args,sop_unknown_identifer,
      sop_invalid_number,sop_too_deeply_nested,sop_not_implemented,sop_too_many_args,
      sop_invalid_assignment,sop_unmatched_open_curly,sop_unmatched_close_curly
};

static char *sop_error_names[] = {
  snd_string_no_error,snd_string_unmatched_open_paren,snd_string_not_enough_args,snd_string_unknown_identifier,
  snd_string_invalid_number,snd_string_too_deeply_nested,snd_string_unimplemented_operation,snd_string_too_many_args,
  snd_string_invalid_assignment,snd_string_unmatched_open_curly,snd_string_unmatched_close_curly
};

int sop_error(void) {return(sop_parse_error != sop_no_error);}

char *sop_parse_error_name(void)
{
  return(sop_error_names[sop_parse_error]);
}

static int scan_tree(sop *tree, int level)
{
  int i;
  switch (tree->op)
    {
    case sop_dot_1: case sop_dot_2: case sop_dot_3: case sop_dot_4: case sop_dot_5: case sop_dot_6: case sop_dot_7: case sop_dot_8: 
      if (level < tree->type) return(tree->type); else return(level);
      break;
    case sop_close_curly:
      for (i=0;i<tree->type;i++) level = scan_tree(((sop **)(tree->op0))[i],level);
      return(level);
      break;
    case sop_num: case sop_var: case sop_snd_var: 
      return(level); 
      break;
    default:
      if (tree->op0) level = scan_tree((sop *)(tree->op0),level);
      if (tree->op1) level = scan_tree((sop *)(tree->op1),level);
      return(level);
    }
  return(level);
}

int scan_tree_for_yn(sop *eval_tree) 
{
  if (eval_tree) return(scan_tree(eval_tree,0));
  return(0);
}

static float sop_func_call(sop *tree) 
{
  /* all callable funcs are assumed to return a float, take n args either floats or an array  of ints + len */
  /* that is, a function can be passed some values from the parsed expression, or the current selection data */
  return(0.0);
}

static void set_sop_var (sop *v, float val); 

float sop_eval(sop *tree)
{
  int i;
  switch (tree->op)
    {
    case sop_num:    return(tree->fval); break;
    case sop_var:    return(tree->fval); break;
    case sop_snd_var: return(get_snd_var(tree->type)); break;
    case sop_func:   return(sop_func_call(tree));
    case sop_plus:   return(sop_eval((sop *)(tree->op0)) + sop_eval((sop *)(tree->op1))); break;
    case sop_minus:  return(sop_eval((sop *)(tree->op0)) - sop_eval((sop *)(tree->op1))); break;
    case sop_divide: return(sop_eval((sop *)(tree->op0)) / sop_eval((sop *)(tree->op1))); break;
    case sop_times:  return(sop_eval((sop *)(tree->op0)) * sop_eval((sop *)(tree->op1))); break;
    case sop_atan:   return(atan2(sop_eval((sop *)(tree->op0)),sop_eval((sop *)(tree->op1)))); break;
    case sop_pow:    return(pow(sop_eval((sop *)(tree->op0)),sop_eval((sop *)(tree->op1)))); break;
    case sop_abs:    return(fabs(sop_eval((sop *)(tree->op0)))); break;
    case sop_log:    return(log(sop_eval((sop *)(tree->op0)))); break;
    case sop_log10:  return(log10(sop_eval((sop *)(tree->op0)))); break;
    case sop_sin:    return(sin(sop_eval((sop *)(tree->op0)))); break;
    case sop_cos:    return(cos(sop_eval((sop *)(tree->op0)))); break;
    case sop_sqrt:   return(sqrt(sop_eval((sop *)(tree->op0)))); break;
    case sop_exp:    return(exp(sop_eval((sop *)(tree->op0)))); break;
    case sop_uminus: return(-(sop_eval((sop *)(tree->op0)))); break;
    case sop_gt:     return((float)(sop_eval((sop *)(tree->op0)) > sop_eval((sop *)(tree->op1)))); break;
    case sop_lt:     return((float)(sop_eval((sop *)(tree->op0)) < sop_eval((sop *)(tree->op1)))); break;
    case sop_geq:    return((float)(sop_eval((sop *)(tree->op0)) >= sop_eval((sop *)(tree->op1)))); break;
    case sop_leq:    return((float)(sop_eval((sop *)(tree->op0)) <= sop_eval((sop *)(tree->op1)))); break;
    case sop_eq:     return((float)(sop_eval((sop *)(tree->op0)) == sop_eval((sop *)(tree->op1)))); break;
    case sop_neq:    return((float)(sop_eval((sop *)(tree->op0)) != sop_eval((sop *)(tree->op1)))); break;
    case sop_close_curly:
      for (i=0;i<tree->type;i++) sop_eval(((sop **)(tree->op0))[i]);
      return(0.0);
      break;
    case sop_dot:    
      if (tree->op0)
	return(search_dot((int)(sop_eval((sop *)(tree->op0))))); 
      else return(search_dot(0));
      break;
    case sop_dot_1: case sop_dot_2: case sop_dot_3: case sop_dot_4: case sop_dot_5: case sop_dot_6: case sop_dot_7: case sop_dot_8: 
      if (tree->op0)
	i = (int)(sop_eval((sop *)(tree->op0)));
      else i = 0;
      return(sop_yn(tree->type - 1,i));
      break;
    case sop_or:     return((float)(((int)sop_eval((sop *)(tree->op0))) || ((int)sop_eval((sop *)(tree->op1))))); break;
    case sop_and:    return((float)(((int)sop_eval((sop *)(tree->op0))) && ((int)sop_eval((sop *)(tree->op1))))); break;
    case sop_not:    return(!(sop_eval((sop *)(tree->op0)))); break;
    case sop_if:     
      if ((int)(sop_eval((sop *)(tree->op0)))) 
	return((sop_eval((sop *)((sop *)(tree->op1))->op0)));
      else return((sop_eval((sop *)((sop *)(tree->op1))->op1)));
      break;
    case sop_acos:   return(acos(sop_eval((sop *)(tree->op0)))); break;
    case sop_asin:   return(asin(sop_eval((sop *)(tree->op0)))); break;
    case sop_cosh:   return(cosh(sop_eval((sop *)(tree->op0)))); break;
    case sop_sinh:   return(sinh(sop_eval((sop *)(tree->op0)))); break;
    case sop_tanh:   return(tanh(sop_eval((sop *)(tree->op0)))); break;
    case sop_fmod:   return(fmod((float)(sop_eval((sop *)(tree->op0))),sop_eval((sop *)(tree->op1)))); break;
    case sop_ceil:   return(ceil((float)(sop_eval((sop *)(tree->op0))))); break;
    case sop_floor:  return(floor((float)(sop_eval((sop *)(tree->op0))))); break;
    case sop_assign: case sop_plus_assign: case sop_minus_assign: case sop_times_assign: case sop_divide_assign: 
      {
	float oldval,newval;
	int offset = 0,opl;
	sop *opl_tree;
	opl_tree = (sop *)(tree->op0);
	opl = opl_tree->op;
	if ((opl != sop_dot) && (opl != sop_var) && (opl != sop_snd_var))
	  {
	    sop_parse_error = sop_invalid_assignment;
	    longjmp(jmpbuf,1);
	  }
	if (opl == sop_dot)
	  {
	    if (opl_tree->op0)
	      offset=sop_eval((sop *)(opl_tree->op0));
	    else offset=0;
	    if (tree->op != sop_assign) oldval = search_dot(offset); else oldval = 0.0;
	  }
	else 
	  {
	    if (opl == sop_snd_var)
	      oldval = get_snd_var(opl_tree->type);
	    else oldval = opl_tree->fval;
	  }
	newval = sop_eval((sop *)(tree->op1));
	switch (tree->op)
	  {
	  case sop_assign:        oldval  = newval;  break;
	  case sop_plus_assign:   oldval += newval;  break;
	  case sop_minus_assign:  oldval -= newval;  break;
	  case sop_times_assign:  oldval *= newval;  break;
	  case sop_divide_assign: oldval /= newval;  break;
	  }
	if (opl == sop_dot)
	  search_dot_assign(offset,oldval);
	else 
	  {
	    if (opl == sop_snd_var)
	      set_snd_var(opl_tree->type,oldval);
	    else set_sop_var(opl_tree,oldval);
	  }
	break;
      }
    }
  return(0.0);
}

static sop **vars;
static char **ids;
static int num_vars = 0;
static int id_table_size = 0;
#define SOP_SIZE_INCREMENT 32

static void set_sop_var (sop *v, float val) 
{
  v->fval = val;
#if (!MAIN_PROGRAM_TEST)
  set_lv_float((char *)(v->op1),val);
#endif
}

static sop *get_id(char *name)
{
  int i;
  float *vf;
  sop *vs;
  if (vars)
    {
      for (i=0;i<num_vars;i++)
	{
	  if (strcmp(name,ids[i]) == 0)
	    {
	      return(vars[i]);
	    }
	}
    }
#if (!MAIN_PROGRAM_TEST)
  vf = find_lv_float(name);
  if (vf)
    {
      vs = (sop *)calloc(1,sizeof(sop));
      vs->op = sop_var;
      vs->type = 0;
      vs->op1 = (void *)copy_string(name);
      vs->fval = (*vf);
      return(vs);
    }
#endif
  return(NULL);
}

static void sop_add_id_1(char *name, int op, int type)
{
  int len,i;
  if (!vars)
    {
      vars = (sop **)calloc(SOP_SIZE_INCREMENT,sizeof(sop *));
      ids = (char **)calloc(SOP_SIZE_INCREMENT,sizeof(char *));
      num_vars = 0;
      id_table_size = SOP_SIZE_INCREMENT;
    }
  else
    {
      if (num_vars >= id_table_size)
	{
	  len = id_table_size;
	  id_table_size += SOP_SIZE_INCREMENT;
	  vars = (sop **)realloc(vars,id_table_size*sizeof(sop *));
	  ids = (char **)realloc(ids,id_table_size*sizeof(char *));
	  for (i=len;i<id_table_size;i++)
	    {
	      vars[i] = NULL;
	      ids[i] = NULL;
	    }
	}
    }
  len = strlen(name);
  ids[num_vars] = (char *)calloc(len+1,sizeof(char));
  strcpy(ids[num_vars],name);
  vars[num_vars] = (sop *)calloc(1,sizeof(sop));
  vars[num_vars]->fval = 0.0;
  vars[num_vars]->op = op;
  vars[num_vars]->type = type;
  vars[num_vars]->op1 = (void *)(ids[num_vars]);
  num_vars++;
#if (!MAIN_PROGRAM_TEST)
  if (op == sop_var) def_float(name);
#endif
}

void sop_add_id(char *name) {sop_add_id_1(name,sop_var,0);}
void sop_add_snd_id(char *name, int type) {sop_add_id_1(name,sop_snd_var,type);}

static void sop_display(sop *tree, int spaces)
{
  int i;
  if (tree)
    {
      for (i=0;i<spaces;i++) fprintf(stderr," ");
      if ((tree->op) == sop_num)
	fprintf(stderr,"%f",tree->fval);
      else 
	{
	  if (tree->op == sop_close_curly)
	    {
	      fprintf(stderr,"{");
	      for (i=0;i<tree->type;i++) sop_display(((sop **)(tree->op0))[i],spaces+2);
	      fprintf(stderr,"}");
	    }
	  else
	    {
	      fprintf(stderr,"%s     (%f)",sop_op_name(tree->op),sop_eval(tree));
	      if (tree->op0) {fprintf(stderr,"\n"); sop_display((sop *)(tree->op0),spaces+2);}
	      if (tree->op1) {fprintf(stderr,"\n"); sop_display((sop *)(tree->op1),spaces+2);}
	    }
	}
    }
  else fprintf(stderr," nil");
  if (spaces == 0) fprintf(stderr,"\n");
}

void free_sop(sop *tree)
{
  int i;
  if ((tree) && (tree->op != sop_var) && (tree->op != sop_snd_var))
    {
      if (tree->op != sop_close_curly)
	{
	  if (tree->op0) free_sop((sop *)(tree->op0));
	  if (tree->op1) free_sop((sop *)(tree->op1));
	  free(tree);
	}
      else
	{
	  for (i=0;i<tree->type;i++) free_sop(((sop **)(tree->op0))[i]);
	  free(tree->op0);
	  free(tree);
	}
    }
}

static sop *make_sop(int op, void *op0, sop *op1, float val, int type)
{
  sop *s;
  s=(sop *)calloc(1,sizeof(sop));
  s->op = op;
  switch (op)
    {
    case sop_open:
      sop_parse_error = sop_too_many_open_parens;
      longjmp(jmpbuf,1);
      break;
    case sop_dot_1: type = 1; break;
    case sop_dot_2: type = 2; break;
    case sop_dot_3: type = 3; break;
    case sop_dot_4: type = 4; break;
    case sop_dot_5: type = 5; break;
    case sop_dot_6: type = 6; break;
    case sop_dot_7: type = 7; break;
    case sop_dot_8: type = 8; break;
    default: break;
    }
  s->op0 = op0;
  s->op1 = op1;
  s->fval = val;
  s->type = type;
  return(s);
}

static void pop_arg_stack(int op, int *opt, sop **arg_stack, int *argt)
{
  /* fprintf(stderr,"pop %s [%d,%d] ",sop_op_name(op),(*opt),(*argt)); */
  if (sop_args(op) == 2)
    {
      if ((*argt)<2)
	{
	  sop_parse_error = sop_not_enough_args;
	  longjmp(jmpbuf,1);
	}
      arg_stack[(*argt)-2]=make_sop(op,arg_stack[(*argt)-2],arg_stack[(*argt)-1],0.0,0);
      (*argt)--;
    }
  else
    {
      if (sop_args(op) == 1)
	{
	  if ((*argt)<1)
	    {
	      sop_parse_error = sop_not_enough_args;
	      longjmp(jmpbuf,1);
	    }
	  arg_stack[(*argt)-1] = make_sop(op,arg_stack[(*argt)-1],NULL,0.0,0);
	}
      else
	{
	  arg_stack[*argt] = make_sop(op,NULL,NULL,0.0,0);
	  (*argt)++;
	}
    }
  (*opt)--;
  /* fprintf(stderr," after pop: [%d,%d] ",(*opt),(*argt)); */
}

static void push_sop(int op, int *op_stack, int *opt, sop **arg_stack, int *argt)
{
  while (((*opt) != 0) && (sop_precd(op_stack[(*opt)-1]) >= sop_precd(op)))
    {
      pop_arg_stack(op_stack[(*opt)-1],opt,arg_stack,argt);
    }
  /* fprintf(stderr,"push %s at %d ",sop_op_name(op),(*opt)); */
  op_stack[(*opt)]=op;
  (*opt)++;
  if ((*opt) == MAX_STACK)
    {
      sop_parse_error = sop_too_deeply_nested;
      longjmp(jmpbuf,1);
    }
}

static void push_sarg(float fval, sop **arg_stack, int *argt)
{
  /* fprintf(stderr,"push %f at %d ",fval,(*argt)); */
  arg_stack[*argt]=make_sop(sop_num,NULL,NULL,fval,0);
  (*argt)++;
  if ((*argt) == MAX_STACK)
    {
      sop_parse_error = sop_too_deeply_nested;
      longjmp(jmpbuf,1);
    }
}

static void push_varg(sop *vp, sop **arg_stack, int *argt)
{
  arg_stack[*argt]=vp;
  (*argt)++;
  if ((*argt) == MAX_STACK)
    {
      sop_parse_error = sop_too_deeply_nested;
      longjmp(jmpbuf,1);
    }
}

static char sop_name[MAX_ID_LEN];
static int sop_name_ctr = 0;
static char sop_number[MAX_ID_LEN];
static int sop_number_ctr = 0;
static int sop_afloat = 0;
static int sop_uop;
static int defining_float = 0;

static void clear_args (int *op_stack, int *opt, sop **arg_stack, int *argt)
{
  int val,op;
  float fval;
  sop *vp;
  op = 0;
  if (sop_name_ctr > 0)
    {
      sop_name[sop_name_ctr]='\0';
      if (defining_float) {sop_add_id(sop_name); defining_float = 0; sop_name_ctr = 0; return;}
      vp = get_id(sop_name);
      if (vp) {push_varg(vp,arg_stack,argt); sop_name_ctr = 0; return;}
      else if (strcmp(sop_name,"abs") == 0) op=sop_abs;
      else if (strcmp(sop_name,"log") == 0) op=sop_log;
      else if (strcmp(sop_name,"sin") == 0) op=sop_sin;
      else if (strcmp(sop_name,"cos") == 0) op=sop_cos;
      else if (strcmp(sop_name,"atan") == 0) op=sop_atan;
      else if (strcmp(sop_name,"exp") == 0) op=sop_exp;
      else if (strcmp(sop_name,"pow") == 0) op=sop_pow;
      else if (strcmp(sop_name,"log10") == 0) op=sop_log10;
      else if (strcmp(sop_name,"sqrt") == 0) op=sop_sqrt;
      else if (strcmp(sop_name,"acos") == 0) op=sop_acos;
      else if (strcmp(sop_name,"asin") == 0) op=sop_asin;
      else if (strcmp(sop_name,"cosh") == 0) op=sop_cosh;
      else if (strcmp(sop_name,"sinh") == 0) op=sop_sinh;
      else if (strcmp(sop_name,"tanh") == 0) op=sop_tanh;
      else if (strcmp(sop_name,"fmod") == 0) op=sop_fmod;
      else if (strcmp(sop_name,"ceil") == 0) op=sop_ceil;
      else if (strcmp(sop_name,"floor") == 0) op=sop_floor;
      else if (strcmp(sop_name,"y") == 0) op=sop_dot;
      else if (strcmp(sop_name,"y1") == 0) op=sop_dot_1;
      else if (strcmp(sop_name,"y2") == 0) op=sop_dot_2;
      else if (strcmp(sop_name,"y3") == 0) op=sop_dot_3;
      else if (strcmp(sop_name,"y4") == 0) op=sop_dot_4;
      else if (strcmp(sop_name,"y5") == 0) op=sop_dot_5;
      else if (strcmp(sop_name,"y6") == 0) op=sop_dot_6;
      else if (strcmp(sop_name,"y7") == 0) op=sop_dot_7;
      else if (strcmp(sop_name,"y8") == 0) op=sop_dot_8;
      else if (strcmp(sop_name,"float") == 0) {defining_float = 1; sop_name_ctr = 0; return;}
      else
	{
	  sop_parse_error = sop_unknown_identifer;
	  longjmp(jmpbuf,1);
	}
      if (op) push_sop(op,op_stack,opt,arg_stack,argt);
      if ((op == sop_dot) || (op >= sop_dot_1)) sop_uop = 0;
      sop_name_ctr = 0;
    }
  else
    {
      if (sop_number_ctr > 0)
	{
	  sop_number[sop_number_ctr]='\0';
	  if (sop_afloat) sscanf(sop_number,"%f",&fval); else {sscanf(sop_number,"%d",&val); fval = (float)val;}
	  push_sarg(fval,arg_stack,argt);
	  sop_number_ctr = 0;
	  sop_afloat = 0;
	  sop_uop = 0;
	}
    }
}

static void push_back_curly(int *op_stack, int *opt, sop **arg_stack, int *argt, int close_it)
{
  /* pop ops until sop_open_curly seen, pop it if close_it */
  int i,j,len;
  sop **lines;
  clear_args(op_stack,opt,arg_stack,argt);
  (*opt)--;
  while (((*opt)>=0) && (op_stack[*opt] != sop_open_curly))
    {
      pop_arg_stack(op_stack[*opt],opt,arg_stack,argt);
    }
  if (((*opt) < 0) || (op_stack[*opt] != sop_open_curly))
    {
      sop_parse_error = sop_unmatched_close_curly;
      longjmp(jmpbuf,1);
    }
  /* now make the progn list for open_curly as an op */
  if (close_it == 0) {(*opt)++; return;}
  len=(*argt);
  lines = (sop **)calloc(len,sizeof(sop *));
  for (i=0,j=(*argt)-len;i<len;i++,j++) lines[i]=arg_stack[j];
  (*argt) -= len;
  arg_stack[*argt]=make_sop(sop_close_curly,lines,NULL,0.0,len);
  (*argt)++;
  sop_uop = 1; /* hmmm */
}

static void push_back_ops(int *op_stack, int *opt, sop **arg_stack, int *argt)
{
  clear_args(op_stack,opt,arg_stack,argt);
  /* pop ops until sop_open seen, pop it, check for 14 op, pop it too */
  (*opt)--;
  while (((*opt)>=0) && (op_stack[*opt] != sop_open))
    {
      pop_arg_stack(op_stack[*opt],opt,arg_stack,argt);
    }
  (*opt)--;
  /* fprintf(stderr,"(pop... %d) ",(*opt)); */
  if (((*opt)>=0) && (sop_precd(op_stack[*opt]) == 14))
    {
      if ((op_stack[*opt] == sop_dot) || (op_stack[*opt] >= sop_dot_1))
	{
	  /* fprintf(stderr,"pop dot at %d ",(*argt));  */
	  arg_stack[(*argt)-1] = make_sop(op_stack[*opt],arg_stack[(*argt)-1],NULL,0.0,0);
	  (*opt)--;
	}
      else pop_arg_stack(op_stack[*opt],opt,arg_stack,argt);
    }
  (*opt)++;
  sop_uop = 0;
  /* fprintf(stderr," after paren: [%d,%d] ",(*opt),(*argt));  */
}

static void push_paren(int *op_stack, int *opt, sop **arg_stack, int *argt)
{
  clear_args(op_stack,opt,arg_stack,argt);
  op_stack[*opt] = sop_open;
  (*opt)++;
  sop_uop = 1;
}

static void push_curly(int *op_stack, int *opt, sop **arg_stack, int *argt)
{
  clear_args(op_stack,opt,arg_stack,argt);
  op_stack[*opt] = sop_open_curly;
  (*opt)++;
  sop_uop = 1;
}

static void append_alpha(char *sp)
{
  sop_name[sop_name_ctr++]=(*sp);
  if (sop_name_ctr == MAX_ID_LEN)
    {
      sop_parse_error = sop_unknown_identifer;
      longjmp(jmpbuf,1);
    }
}

static void append_number(char *sp)
{
  if (sop_name_ctr)
    append_alpha(sp);
  else 
    {
      sop_number[sop_number_ctr++]=(*sp);
      if (sop_number_ctr == MAX_ID_LEN)
	{
	  sop_parse_error = sop_invalid_number;
	  longjmp(jmpbuf,1);
	}
      if ((*sp) == '.') 
	{
	  if (sop_afloat)
	    {
	      sop_parse_error = sop_invalid_number;
	      longjmp(jmpbuf,1);
	    }
	  sop_afloat = 1;
	}
    }
}

static int handle_other(char *sp, int *op_stack, int *opt, sop **arg_stack, int *argt)
{
  int op,inc;
  clear_args(op_stack,opt,arg_stack,argt);
  op = 0;
  inc = 0;
       if ((*sp) == '+') {if ((*(sp+1)) == '=') {op=sop_plus_assign; inc=1;} else op=sop_plus;}
  else if ((*sp) == '-') {if ((*(sp+1)) == '=') {op=sop_minus_assign; inc=1;} else {if (sop_uop) op=sop_uminus; else op=sop_minus;}}
  else if ((*sp) == '*') {if ((*(sp+1)) == '=') {op=sop_times_assign; inc=1;} else op=sop_times;}
  else if ((*sp) == '/') {if ((*(sp+1)) == '=') 
                             {op=sop_divide_assign; inc=1;} 
                           else {if ((*(sp+1)) == '*') 
                           {inc=2; while (((*(sp+inc)) != '*') || ((*(sp+inc+1)) != '/')) inc++; inc++;}
                                 else op=sop_divide;}}
  else if ((*sp) == '>') {if ((*(sp+1)) == '=') {op=sop_geq; inc=1;} else op=sop_gt;}
  else if ((*sp) == '<') {if ((*(sp+1)) == '=') {op=sop_leq; inc=1;} else op=sop_lt;}
  else if ((*sp) == '=') {if ((*(sp+1)) == '=') {op=sop_eq; inc=1;} else op=sop_assign;}
  else if ((*sp) == '!') {if ((*(sp+1)) == '=') {op=sop_neq; inc=1;} else op=sop_not;}
  else if ((*sp) == '|') {if ((*(sp+1)) == '|') {op=sop_or; inc=1;} else {sop_parse_error = sop_not_implemented; longjmp(jmpbuf,1);}}
  else if ((*sp) == '&') {if ((*(sp+1)) == '&') {op=sop_and; inc=1;} else {sop_parse_error = sop_not_implemented; longjmp(jmpbuf,1);}}
  else if ((*sp) == '?') op=sop_if;
  else if ((*sp) == ':') op=sop_colon;
  else if ((*sp) == ';') push_back_curly(op_stack,opt,arg_stack,argt,0);
  else if ((*sp) == ')') push_back_ops(op_stack,opt,arg_stack,argt);
  else if ((*sp) == '(') push_paren(op_stack,opt,arg_stack,argt);
  else if ((*sp) == '{') push_curly(op_stack,opt,arg_stack,argt);
  else if ((*sp) == '}') push_back_curly(op_stack,opt,arg_stack,argt,1);
  if (op) {push_sop(op,op_stack,opt,arg_stack,argt); sop_uop = 1;}
  return(inc);
}

static int *op_stack = NULL;
static sop **arg_stack = NULL;

sop *sop_parse(char *expr)
{
  char *sp;
  int err;
  int opt = 0;
  int argt = 0;
  int skip = 0;
  sop_name_ctr = 0;
  sop_number_ctr = 0;
  sop_afloat = 0;
  sop_parse_error = sop_no_error;
  err = setjmp(jmpbuf);
  if (err != 0) return(NULL); /* caller will have to look at sop_parse_error to find out what went awry */
  sop_uop = 1;
  if (!op_stack) op_stack = (int *)calloc(MAX_STACK,sizeof(int));
  if (!arg_stack) arg_stack = (sop **)calloc(MAX_STACK,sizeof(sop *));
  sp=expr;
  while (*sp)
    {
      /* if (*sp) fprintf(stderr,"%c ",(*sp)); */
      if (isspace(*sp)) clear_args(op_stack,&opt,arg_stack,&argt);
      else if ((isalpha(*sp)) || ((*sp) == '_')) append_alpha(sp);
      else if ((isdigit(*sp)) || ((*sp) == '.')) append_number(sp);
      else {skip = handle_other(sp,op_stack,&opt,arg_stack,&argt); if (skip) sp+=skip;}
      sp++;
    }
  clear_args(op_stack,&opt,arg_stack,&argt);
  while (opt > 0)
    {
      pop_arg_stack(op_stack[opt-1],&opt,arg_stack,&argt);
    }
  if (!(arg_stack[0])) return(NULL);
  if (arg_stack[0]->op == sop_open) {sop_parse_error = sop_too_many_open_parens; return(NULL);}
  if (argt > 1) {sop_parse_error = sop_too_many_args; return(NULL);}
  return(arg_stack[0]);
}

#if (MAIN_PROGRAM_TEST)

#define SOP_TESTS 66

char *exprs[] = {"-2","3+2","3/2","3*2","3 < 2","3 <= 2","3 == 2","3 != 2","3 > 2","3 >= 2",
		 "log(8.0)/log(2.0)","log10(100.0)","sin(0.0)","cos(0.0)","abs(1.0)","3*2+1",
		 "3+2*1","3*2*1","3*2/2","3*(2+2)","3-(2*2)","sqrt(4.0)","pow(2.0,3.0)","1+((3*2)-(2*2))",
		 "3.5 +    .5","-2.0-1.0","-(2+2)","-(-(-(-3)))","-2-1","-3*-2","3*2-4*3","3*2-4*3+4/2","4*3+4/2",
		 "(3>2) && (4<5)","(3>2) || (4<3)","1+2+3+4+5*1",".1 + -.2","(((-3)))","3/(1+2)","(1+2)/(3*1)",
		 "abs(cos(3.14159/2)) < 0.001","asin(0.0)","(abs(asin(1.0) - 3.14159/2) < .0001)",
		 "abs(acos(1.0)) < .0001","sinh(0.0)","ceil(0.1)","floor(0.1)","cosh(0.0)",
		 "fmod(4.0,2.0)","(.1>.2) ? 31.0 : 32.0","y=1.0","y(3) = 2*3","y(0)+1.0+y(0)",
		 "fmod(log10(10000.0),log(4.0)/log(2.0))","-2+3","-y(1)+2","-sin(0.0)",
		 "y(1) = y+.1","hi","hi+3","sin(ho)","hi = 3.0","hi+ho","{hi=4.0;ho=hi+1;}","ho",
		 "hi = /* a comment */ 1.0"
};

float results[] = {-2.0,5.0,1.5,6.0,0.0,0.0,0.0,1.0,1.0,1.0,
		   3.0,2.0,0.0,1.0,1.0,7.0,
		   5.0,6.0,3.0,12.0,-1.0,2.0,8.0,3.0,
		   4.0,-3.0,-4.0,3.0,-3.0,6.0,-6.0,-4.0,14.0,
		   1.0,1.0,15.0,-0.1,-3.0,1.0,1.0,
		   1.0,0.0,1.0,
		   1.0,0.0,1.0,0.0,1.0,
		   0.0,32.0,1.0,6.0,1.0,
		   0.0,1.0,1.0,0.0,
		   0.1,0.0,3.0,0.0,3.0,3.0,0.0,5.0,
		   1.0
};

int main(int argc, char **argv)
{
  int i;
  float val;
  sop *tree;
  tree = sop_parse("float hi"); if (tree) sop_eval(tree);
  tree = sop_parse("float ho"); if (tree) sop_eval(tree);
  tree = sop_parse("hi=0.0"); if (tree) sop_eval(tree);
  tree = sop_parse("ho=0.0"); if (tree) sop_eval(tree);
  for (i=0;i<SOP_TESTS;i++)
    {
      tree = sop_parse(exprs[i]);
      if (tree) val=sop_eval(tree); else fprintf(stderr,"parser error %d %s ",sop_parse_error,sop_parse_error_name());
      if ((!tree) || (val != results[i]))
	{
	  fprintf(stderr,"%s => %f != %f\n",exprs[i],val,results[i]);
	  if (tree) sop_display(tree,0);
	}
      else fprintf(stderr,".");
      }
  fprintf(stderr,"\n");
}
#endif
