/*  **************************************************************************
 * 
 * --- File: read_token.c
 * 
 * --- Purpose: provides a function for reading tokens from stdin.
 * 
 * --- Copyright (C) Guido Gonzato, guido@ibogeo.df.unibo.it
 * 
 * --- Last updated: 3 May 2001
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 * ************************************************************************ */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include "gexpr.h"

#define	ZERO	1E-10F
#define N_ELEMS(vet) (sizeof(vet) / sizeof(vet[0]))

/* ----- */

static int get_char (void);
static void unget_char (int);
static int binsearch (const char *[], int, const char *);
static void find_out (TOKEN *);
static double fact (double);	/* these functions are handy */
static double my_rand (double);
static double rnd (double);
static double sqr (double);

static int cur_pos = 0;		/* current position */
static BOOL input_over = FALSE;
static int last_function;

static const char *s_constants[] = 
{"M_1_PI", "M_2_PI", "M_2_SQRTPI", "M_E", "M_LN10", "M_LN2", "M_LOG10E", 
 "M_LOG2E", "M_PI", "M_PI_2", "M_PI_4", "M_SQRT1_2", "M_SQRT2", "PI", "PI2"
};

static double v_constants[] = 
{0.31830988618379067154, 0.63661977236758134308,
 1.12837916709551257390, 2.7182818284590452354, 
 2.30258509299404568402, 0.69314718055994530942, 
 0.43429448190325182765, 1.4426950408889634074, 
 3.14159265358979323846, 1.57079632679489661923, 
 0.78539816339744830962, 0.70710678118654752440, 
 1.41421356237309504880, 3.14159265358979323846, 
 1.57079632679489661923
};
static const int n_constants = 15;

enum func 
{ACOS, ASIN, ATAN, ATAN2, CEIL, COS, COSH, EXP, FABS, 
 FACT, FLOOR, FMOD, LDEXP, LOG, LOG10, POW, RAND, RND, 
 SIN, SINH, SQR, SQRT, TAN, TANH
};

static const char *s_functions[] = 
{"acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "exp", "fabs", 
 "fact", "floor", "fmod", "ldexp", "log", "log10", "pow", "rand", "rnd", 
 "sin", "sinh", "sqr", "sqrt", "tan", "tanh"
};
static const int n_functions = 24;

static short f_arguments[] = 
{1, 1, 1, 2, 1, 1, 1, 1, 1,
 1, 1, 2, 2, 1, 1, 2, 1, 1,
 1, 1, 1, 1, 1, 1
};

static const char *s_commands[] = {"base", "dec", "decimals", "help", "quit"
};
static const int n_commands = 5;

typedef double (*FDPTR)();

static FDPTR v_functions[] = 
{acos, asin, atan, atan2, ceil, cos, cosh, exp, fabs,
 fact, floor, fmod, ldexp, log, log10, pow, my_rand, rnd, 
 sin, sinh, sqr, sqrt, tan, tanh
};

/* ----- */

int get_char(void)
{
  int ch;
  
  if (input_over)
    ch = EOF;
  else {
    if (input_source == STDIN)
      ch = getchar();
    else {
      ch = expression_source[cur_pos];
      cur_pos++;
      if (ch == '\0') {
	ch = '\n';
	input_over = TRUE;
      }
    }
  }
  
  return ch;

} /* get_char() */

/* ----- */

void unget_char(int ch)
{
  
  if (input_source == STDIN)
    ungetc(ch, stdin);
  else {
    cur_pos--;
    if (input_over)
      input_over = FALSE;
  }

} /* unget_char() */

/* ----- */
  
void check_token(TOKEN_TYPE t1, TOKEN_TYPE t2)
{
  
  char s[100];
  char *whats_missing[] = {"(none)", "(none)", "end", "end of input", 
      "end of line", "`,'", "number", "identifier", "constant", 
      "function", "command", "(none)", "`+'", "`-'", "`*'", "`/'", 
      "`('", "`)'"
  };
  
  if (t1 != t2) {
    sprintf(s, "%s expected", whats_missing[(int) t2]);
    error(s, t1);
  }
  
} /* check_token() */

/* ----- */

/* read_token() reads the next available token from stdin */

TOKEN read_token(void)
{
  int ch, i = 0;
  static TOKEN tok;	/* token read */
  
  if (token_read == TRUE) {
    token_read = FALSE;
    return(tok);
  }
  
  /* otherwise, read a new token */
  
  tok.type = T_NONE;
  
  /* skip spaces - don't use isspace(), because it skips '\n' */
  
  while ((ch = get_char()) == ' ')
    ;
  
  switch (ch) {
    
    /* check T_END */
    
   case 'q':
   case EOF:
    tok.type = T_END;
    break;
    
    /* check T_COMMA */
    
   case ',':
    tok.type = T_COMMA;
    break;
    
    /* check EOL */
    
   case '\n':
    tok.type = T_EOL;
    break;
    
    /* check if digit */
    
   case '0':
   case '1':
   case '2':
   case '3':
   case '4':
   case '5':
   case '6':
   case '7':
   case '8':
   case '9':
   case '.': 
    tok.type = T_DIGIT;
    tok.string[i++] = ch;
    tok.string[i] = '\0';
    while (isdigit(ch = get_char()) 
	   || ch == '.' || ch == 'E'
	   || ch == '-'                  /* e.g. 1.2E5 */
	   || ch == 'b' || ch == 'x'     /* e.g. 0x23, 0b1101 */
	   || (ch >= 'a' && ch <= 'f'))  /* e.g. 0xffa3 */
      if (i < MAX_TOKEN_LENGTH) {
  	tok.string[i++] = ch;
	tok.string[i] = '\0';
      }
      else {
	tok.type = T_ERROR; 	/* digit too long */
	break;
      }
    unget_char(ch);
    break;
    
    /* check if symbol */
    
   case '+':
   case '-':
   case '*':
   case '/':
   case '(':
   case ')':
    tok.type = T_MISC;
    tok.string[i++] = ch;
    tok.string[i] = '\0';
    break;
    
   case '=':
   case '<':
   case '>':
    tok.type = T_REL;
    tok.string[i++] = ch;
    ch = get_char();	/* must be (for =) or can be (for < >) '=' */
    if (tok.string[0] == '=') {
      if (ch != '=') {
        tok.type = T_ERROR;
        error("'\"' expected.", tok.type);
      }
      else {
	tok.string[i++] = ch;
        tok.string[i] = '\0';
      }
    } /* if (tok.string[0] == '=') */
    else {
      if (ch == '=') {
        tok.string[i++] = ch;
        tok.string[i] = '\0';
      }
      else {
	unget_char(ch);
	tok.string[i++] = ch;
        tok.string[i] = '\0';
      }
    } /* else */
    break;
    
   default:
    /* check if alphanumeric */
    if (isalpha(ch)) {
      tok.type = T_IDENT;
      tok.string[i++] = ch;
      tok.string[i] = '\0';
      while (isalpha(ch = get_char()) || isdigit(ch) || ch == '_')
        if (i < MAX_TOKEN_LENGTH) {
	  tok.string[i++] = ch;
	  tok.string[i] = '\0';
        }
        else {
	  tok.type = T_ERROR; 	/* identifier too long */
	  break;
        }
      unget_char(ch);
    } /* if (isalpha(ch) ... */
    /* the rest is just plain wrong! */
    else {
      tok.type = T_ERROR;
      error("unknown token.", tok.type);
    }
    
  } /* switch */

  /* now check if the token is ok  */
  
  find_out(&tok);
  return tok;
  
} /* read_token() */

/* ----- */

/* find_out() checks the tokens and assigns values */

static void find_out(TOKEN *tok)
{
  char *err, *substr;
  char misc[] = "+-*/()";
  char rel[] = "==<=< >=> ";
  int position;
  
  /* if it's a digit, check it */
  
  if (tok->type == T_DIGIT) {
    tok->value = my_strtod(tok->string, &err);
    if (*err != '\0') 
      tok->type = T_ERROR;
  }

  /* if it's an identifier, check what it is */
  
  if (tok->type == T_IDENT) { /* is this a constant?  */
    position = binsearch(s_constants, N_ELEMS(s_constants), tok->string);
    if (position != -1) {
      tok->type = T_CONST;
      tok->value = v_constants[position];
    }
    else { /* is this a command? */
      position = binsearch(s_commands, N_ELEMS(s_commands), tok->string);
      if (position != -1)
	tok->type = T_COMMAND;
      else { /* is this a function? */
	position = binsearch(s_functions, N_ELEMS(s_functions), tok->string);
	if (position != -1) {
	  last_function = position;
	  tok->type = T_FUNCTION;
	  tok->args = f_arguments[position];
	}
	else
	  tok->type = T_ERROR;
      } /* else  - function */
    } /* else - command */
  } /* if (tok->type == T_IDENT) */
  
  /* if it belongs to the "misc", check it's ok */
  
  if (tok->type == T_MISC) {
    substr = (char *) strchr(misc, tok->string[0]);
    if (substr == NULL)  /* token not existent  - !!! redundant */
      tok->type = T_ERROR;
    else
      tok->type = T_MISC + 1 + (substr - misc);
  }
  
  /* if it belongs to the relationals, check it's ok */
  
  if (tok->type == T_REL) {
    substr = (char *) strstr(rel, tok->string);
    if (substr == NULL)  /* token not existent  - !!! redundant */
      tok->type = T_ERROR;
    else
      tok->type = T_REL + 1 + (substr - rel) / 2;
  }
  
} /* find_out() */

/* ----- */

/* binsearch() is similar to bsearch(), but returns the position of the
 * target (or -1 if not found)
 */

static int binsearch(const char *a[], int size, const char *target)
{

  int i, j, m;
  
  i = 0;
  j = size - 1;
  m = (i + j) / 2;
  
  do {
    
    m = (i + j) / 2;

    if (strcmp(target, a[m]) > 0)
      i = m + 1;
    else
      j = m - 1;
    
  } while ( (strcmp(target, a[m]) != 0) && (i <= j));
  
  if (strcmp(target, a[m]) == 0)
    return m;
  else
    return -1;

} /* binsearch() */

/* ----- */

double function_value(short args, double a1, double a2)
{
  double s;

  if (args == 1) {
    switch (last_function) {
     case ACOS:
     case ASIN: if ( !(-1 <= a1 && a1 <= 1))
	error("argument must be >= -1 and <= 1", T_NONE);
      break;
     case LOG:
     case LOG10: if (a1 <= ZERO)
	error("argument must be > 0", T_NONE);
      break;
     case SQRT: if (a1 < 0.0)
	error("argument must be >= 0", T_NONE);
      break;
     case TAN: if (fabs(cos(a1)) < 0.0000000001) /* !!! */
	error("function not defined at this value", T_NONE);
    } /* switch */
    s = v_functions[last_function](a1);
  } /* if (args == 1) */
  else {
    switch (last_function) {
     case ATAN2: 
     case FMOD: if (a2 == ZERO) /* !!! */
	error("second argument must be != 0", T_NONE);
      break;
     case POW: if (a1 < ZERO && (a2 - floor(a2) > ZERO)) /* !!! */
	error("if arg1 < 0, arg2 must be an integer", T_NONE);
    }
    if (last_function == LDEXP)
      s = v_functions[last_function](a1, (int) a2);
    else
      s = v_functions[last_function](a1, a2);
  } /* else */
  
  return s;
  
} /* function_value() */

/* ----- */

static double fact(double n)
{
  int i;
  double f = 1;
  
  for (i = 1; i <= n; i++)
    f *= i;
  
  return f;
  
} /* fact() */

/* ----- */

static double my_rand(double d)
{

  srand ((unsigned int) d);
  return (double) rand();
  
} /* my_rand() */


static double rnd(double d)
{
  
  return (double) rand() / (double) RAND_MAX * d;
  
} /* rnd() */

/* ----- */

static double sqr(double d)
{
  
  return d*d;
  
} /* sqr() */

/* ----- */

/* --- End of file read_token.c --- */


syntax highlighted by Code2HTML, v. 0.9.1