/* calc.c (emx+gcc) -- Copyright (c) 1992-1996 by Eberhard Mattes */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <math.h>
#include <float.h>
#include <ctype.h>

#define NORETURN2 __attribute__ ((noreturn))

#define FALSE 0
#define TRUE  1

#define T_EOF           0
#define T_NAME          1
#define T_NUMBER        2
#define T_LPAR          3
#define T_RPAR          4
#define T_ASSIGN        5
#define T_OPERATOR      6
#define T_EOL           7
#define T_SET           8
#define T_HELP          9
#define T_QUIT          10

struct keyword
{
  char *name;
  int token;
  double (*unary)(double x);
  double (*binary)(double x, double y);
  int u_bp, b_bp;
};

struct var
{
  struct var *next;
  char *name;
  double value;
};

static FILE *parse_f;
static char parse_buffer[512];
static char tokstr[512];
static const unsigned char *parse_ptr;
static int parse_line;
static int parse_cont;
static int interactive;
static int token;
static int calc_errno;
static const struct keyword *tokptr;
static double toknum;
static jmp_buf main_loop;
static struct var *var_root;

static double f_add (double x, double y);
static double f_sub (double x, double y);
static double f_mul (double x, double y);
static double f_div (double x, double y);
static double f_shl (double x, double y);
static double f_shr (double x, double y);
static double f_and (double x, double y);
static double f_or (double x, double y);
static double f_eq (double x, double y);
static double f_ne (double x, double y);
static double f_lt (double x, double y);
static double f_le (double x, double y);
static double f_gt (double x, double y);
static double f_ge (double x, double y);
static double f_neg (double x);
static double f_not (double x);
static double expr (void);
static double expr1 (int min_bp);


static struct keyword keywords[] =
{
  {"+",         T_OPERATOR, NULL,  f_add,  0, 10},
  {"++",        T_OPERATOR, NULL,  hypot,  0, 10},
  {"-",         T_OPERATOR, f_neg, f_sub, 12, 10},
  {"*",         T_OPERATOR, NULL,  f_mul,  0, 14},
  {"/",         T_OPERATOR, NULL,  f_div,  0, 14},
  {"%",         T_OPERATOR, NULL,  fmod,   0, 14},
  {"=",         T_OPERATOR, NULL,  f_eq,   0,  8},
  {"<>",        T_OPERATOR, NULL,  f_ne,   0,  8},
  {"!=",        T_OPERATOR, NULL,  f_ne,   0,  8},
  {"<",         T_OPERATOR, NULL,  f_lt,   0,  8},
  {"<=",        T_OPERATOR, NULL,  f_le,   0,  8},
  {">",         T_OPERATOR, NULL,  f_gt,   0,  8},
  {">=",        T_OPERATOR, NULL,  f_ge,   0,  8},
  {"^",         T_OPERATOR, NULL,  pow,    0, 21},
  {"**",        T_OPERATOR, NULL,  pow,    0, 21},
  {"<<",        T_OPERATOR, NULL,  f_shl,  0, 16},
  {">>",        T_OPERATOR, NULL,  f_shr,  0, 16},
  {"&",         T_OPERATOR, NULL,  f_and,  0,  4},
  {"|",         T_OPERATOR, NULL,  f_or,   0,  2},
  {"!",         T_OPERATOR, f_not, NULL,   6,  0},
  {"(",         T_LPAR,     NULL,  NULL,   0,  0},
  {")",         T_RPAR,     NULL,  NULL,   0,  0},
  {"or",        T_OPERATOR, NULL,  f_or,   0,  2},
  {"and",       T_OPERATOR, NULL,  f_and,  0,  4},
  {"not",       T_OPERATOR, f_not, NULL,   6,  0},
  {"exp",       T_OPERATOR, exp,   NULL,  18,  0},
  {"log",       T_OPERATOR, log10, NULL,  18,  0},
  {"ln",        T_OPERATOR, log,   NULL,  18,  0},
  {"cos",       T_OPERATOR, cos,   NULL,  18,  0},
  {"sin",       T_OPERATOR, sin,   NULL,  18,  0},
  {"tan",       T_OPERATOR, tan,   NULL,  18,  0},
  {"cosh",      T_OPERATOR, cosh,  NULL,  18,  0},
  {"sinh",      T_OPERATOR, sinh,  NULL,  18,  0},
  {"tanh",      T_OPERATOR, tanh,  NULL,  18,  0},
  {"acos",      T_OPERATOR, acos,  NULL,  18,  0},
  {"asin",      T_OPERATOR, asin,  NULL,  18,  0},
  {"atan",      T_OPERATOR, atan,  NULL,  18,  0},
  {"cbrt",      T_OPERATOR, cbrt,  NULL,  18,  0},
  {"sqrt",      T_OPERATOR, sqrt,  NULL,  18,  0},
  {"ceil",      T_OPERATOR, ceil,  NULL,  18,  0},
  {"floor",     T_OPERATOR, floor, NULL,  18,  0},
  {"round",     T_OPERATOR, rint,  NULL,  18,  0},
  {"trunc",     T_OPERATOR, trunc, NULL,  18,  0},
  {"abs",       T_OPERATOR, fabs,  NULL,  18,  0},
  {"set",       T_SET,      NULL,  NULL,   0,  0},
  {"help",      T_HELP,     NULL,  NULL,   0,  0},
  {"quit",      T_QUIT,     NULL,  NULL,   0,  0},
  {NULL,        0,          NULL,  NULL,   0,  0}
};


static void *xmalloc (size_t n)
{
  void *p = malloc (n);
  if (p == NULL)
    {
      fprintf (stderr, "Out of memory\n");
      exit (2);
    }
  return p;
}


static void syntax (const char *msg)
{
  fprintf (stderr, "%s (Token %s, line %d)\n", msg, tokstr, parse_line);
  if (interactive)
    longjmp (main_loop, 1);
  else
    exit (2);
}


static int get_line (void)
{
  char *p;

  if (interactive != 0)
    {
      if (parse_cont)
        printf (">> ");
      else
        printf ("%d> ", interactive++);
      fflush (stdout);
    }
  if (fgets (parse_buffer, sizeof (parse_buffer), parse_f) == NULL)
    {
      if (!ferror (parse_f))
        return FALSE;
      perror ("fgets");
      exit (2);
    }
  p = strchr (parse_buffer, '\n');
  if (p != NULL) *p = 0;
  parse_ptr = parse_buffer;
  ++parse_line; parse_cont = TRUE;
  return TRUE;
}


static int find_token (const struct keyword *table)
{
  while (table->name != NULL)
    if (strcmp (tokstr, table->name) == 0)
      {
        token = table->token;
        tokptr = table;
        return TRUE;
      }
    else
      ++table;
  return FALSE;
}


static int find_prefix (void)
{
  size_t len;
  int j;
  const struct keyword *table;
  
  len = strlen (tokstr); j = 0;
  for (table = keywords; table->name != NULL; ++table)
    if (strlen (table->name) >= len &&
        memcmp (table->name, tokstr, len) == 0)
      ++j;
  return j;
}


static int find_exact (void)
{
  size_t len;
  int j;
  const struct keyword *table;
  
  len = strlen (tokstr); j = 0;
  for (table = keywords; table->name != NULL; ++table)
    if (strlen (table->name) == len && strcmp (table->name, tokstr) == 0)
      {
        token = table->token;
        tokptr = table;
        ++j;
      }
  return j;
}

static void get_token (int more)
{
  const unsigned char *start;
  char *p;
  size_t len;
  int pfx;
  
  for (;;)
    {
      if (*parse_ptr == ' ' || *parse_ptr == '\t')
        ++parse_ptr;
      else if (*parse_ptr == 0 || *parse_ptr == '#')
        {
          if (!more)
            {
              strcpy (tokstr, "<<EOL>>");
              token = T_EOL;
              return;
            }
          if (!get_line ())
            {
              strcpy (tokstr, "<<EOF>>");
              token = T_EOF;
              return;
            }
        }
      else
        break;
    }
  if (*parse_ptr >= 0x80 || isalpha (*parse_ptr))
    {
      start = parse_ptr++;
      while (*parse_ptr >= 0x80 || isalnum (*parse_ptr) || *parse_ptr == '_')
        ++parse_ptr;
      len = parse_ptr - start;
      if (len + 1 > sizeof (tokstr))
        syntax ("Name too long");
      memcpy (tokstr, start, len);
      tokstr[len] = 0;
      if (!find_token (keywords))
        token = T_NAME;
    }
  else if (*parse_ptr == '.' || isdigit (*parse_ptr))
    {
      errno = 0;
      toknum = strtod (parse_ptr, &p);
      if (errno != 0)
        syntax ("Invalid number");
      token = T_NUMBER;
      len = p - (char *)parse_ptr + 1;
      if (len > sizeof (tokstr))
        len = sizeof (tokstr);
      _strncpy (tokstr, parse_ptr, len);
      parse_ptr = p;
    }
  else
    {
      tokstr[0] = *parse_ptr++;
      len = 1;
      for (;;)
        {
          tokstr[len] = 0;
          pfx = find_prefix ();
          if (pfx == 0 || *parse_ptr == 0)
            break;
          tokstr[len++] = *parse_ptr++;
        }
      while (find_exact () != 1)
        {
          if (len == 1)
            syntax ("Invalid character");
          --len; --parse_ptr;
          tokstr[len] = 0;
        }
    }
}


static void check (int t, const char *msg)
{
  while (token == T_EOL)
    get_token (TRUE);
  if (token != t)
    {
      char buf[100];

      sprintf (buf, "`%s' expected", msg);
      syntax (buf);
    }
}


static struct var *find_var (const char *name)
{
  struct var *p;

  for (p = var_root; p != NULL; p = p->next)
    if (strcmp (p->name, name) == 0)
      return p;
  return NULL;
}


static void set_var (const char *name, double value)
{
  struct var *p;

  p = find_var (name);
  if (p == NULL)
    {
      p = xmalloc (sizeof (*p));
      p->name = xmalloc (strlen (name) + 1);
      strcpy (p->name, name);
      p->next = var_root;
      var_root = p;
    }
  p->value = value;
}


static double factor (void)
{
  double x;
  const struct keyword *ptr;
  const struct var *v;

  switch (token)
    {
    case T_NUMBER:
      x = toknum;
      get_token (FALSE);
      return x;
    case T_NAME:
      v = find_var (tokstr);
      if (v == NULL)
        syntax ("Variable not set");
      get_token (FALSE);
      return v->value;
    case T_LPAR:
      get_token (TRUE);
      x = expr ();
      check (T_RPAR, ")");
      get_token (FALSE);
      return x;
    case T_OPERATOR:
      if (tokptr->unary == NULL)
        syntax ("Unary operator expected");
      ptr = tokptr;
      get_token (TRUE);
      x = expr1 (ptr->u_bp);
      errno = 0;
      x = ptr->unary (x);
      if (errno != 0)
        calc_errno = errno;
      return x;
    default:
      syntax ("Operand expected");
      return 0.0;
    }
}


static double expr1 (int min_bp)
{
  double x, y;
  const struct keyword *ptr;

  min_bp &= ~1;
  x = factor ();
  while (token == T_OPERATOR && tokptr->binary != NULL &&
         tokptr->b_bp > min_bp)
    {
      ptr = tokptr;
      get_token (TRUE);
      y = expr1 (ptr->b_bp);
      errno = 0;
      x = ptr->binary (x, y);
      if (errno != 0)
        calc_errno = errno;
    }
  return x;
}


static double expr (void)
{
  return expr1 (0);
}


static double f_add (double x, double y)
{
  return x + y;
}


static double f_sub (double x, double y)
{
  return x - y;
}


static double f_mul (double x, double y)
{
  return x * y;
}


static double f_div (double x, double y)
{
  return x / y;
}


static double f_shl (double x, double y)
{
  return ldexp (x, (int)y);
}


static double f_shr (double x, double y)
{
  return ldexp (x, (int)(-y));
}


static double f_eq (double x, double y)
{
  return x == y ? 1.0 : 0.0;
}


static double f_ne (double x, double y)
{
  return x != y ? 1.0 : 0.0;
}


static double f_lt (double x, double y)
{
  return x < y ? 1.0 : 0.0;
}


static double f_le (double x, double y)
{
  return x <= y ? 1.0 : 0.0;
}


static double f_gt (double x, double y)
{
  return x > y ? 1.0 : 0.0;
}


static double f_ge (double x, double y)
{
  return x >= y ? 1.0 : 0.0;
}


static double f_neg (double x)
{
  return -x;
}


static double f_and (double x, double y)
{
  return x != 0.0 && y != 0.0 ? 1.0 : 0.0;
}


static double f_or (double x, double y)
{
  return x != 0.0 || y != 0.0 ? 1.0 : 0.0;
}


static double f_not (double x)
{
  return x == 0.0 ? 1.0 : 0.0;
}


static void help (void)
{
  puts ("This is the emx calculator. "
        "Copyright (c) 1992-1996 by Eberhard Mattes.\n");
  puts ("Commands:");
  puts ("  help           display help");
  puts ("  quit           exit the emx calculator");
  puts ("  set <v> <exp>  set variable");
  puts ("  <exp>          evaluate the expression <exp> and print it");
  puts ("Operators: (ordered by precedence):");
  puts ("  ( )            change order of evaluation");
  puts ("  ^ **           compute power");
  puts ("  sin cos tan sinh cosh tanh asin acos atan exp log ln");
  puts ("      cbrt sqrt abs ceil floor round trunc");
  puts ("  << >>          shift left, shift right");
  puts ("  * / %          multiplication, division, modulus");
  puts ("  -              change sign");
  puts ("  + - ++         addition, subtraction, hypot");
  puts ("  = != < <= > >= compare numbers");
  puts ("  ! not          logical not");
  puts ("  & and          logical and");
  puts ("  | or           logical or");
  puts ("All operators but ^ and ** are left-associative");
  puts ("Built-in variables: pi e last");
}


static double eval_and_print (void)
{
  double x;

  x = expr ();
  if (token != T_EOL)
    syntax ("extra characters at end of line");
  if (interactive)
    printf ("==> ");
  printf ("%g", x);
  if (calc_errno != 0)
    printf (" (%s)", strerror (calc_errno));
  printf ("\n");
  set_var ("last", x);
  return x;
}


static void set_command (void)
{
  char *name;

  get_token (FALSE);
  if (token != T_NAME)
    syntax ("Variable name expected"),
  name = alloca (strlen (tokstr) + 1);
  strcpy (name, tokstr);
  get_token (FALSE);
  set_var (name, eval_and_print ());
}


int main (int argc, char *argv[])
{
  if (argc == 1)
    {
      parse_f = stdin;
      interactive = 1;
    }
  else if (argc == 2)
    {
      interactive = 0;
      parse_f = fopen (argv[1], "rt");
      if (parse_f == NULL)
        {
          fprintf (stderr, "Cannot open input file %s\n", argv[1]);
          return 2;
        }
    }
  else
    {
      fprintf (stderr, "Usage: calc [filename]\n");
      return 1;
    }
  parse_line = 0;
  var_root = NULL;
  set_var ("pi", M_PI);
  set_var ("e", M_E);
  if (interactive)
    printf ("Type `help' for help.\n");
  for (;;)
    {
      setjmp (main_loop);
      if (interactive)
        printf ("\n");
      parse_cont = FALSE;
      parse_buffer[0] = 0; parse_ptr = parse_buffer;
      calc_errno = 0;
      get_token (TRUE);
      if (token == T_EOF || token == T_QUIT)
        break;
      else if (token == T_HELP)
        help ();
      else if (token == T_SET)
        set_command ();
      else
        eval_and_print ();
    }
  return 0;
}
