CCF's Expression Evaluator

Just your usual twin-stack infix to RPN convertion, really, with execution being done during the operator reduction step.

#include	<ctype.h>
#include	<limits.h>
#include	<stddef.h>
#include	<string.h>

#include	"expr.h"
#include	"iofns.h"
#include	"memfns.h"
#include	"syms.h"


static long	*int_stack;
static size_t	 int_depth, max_int_depth;

typedef struct
{
  char	 *name;
  int	  priority;
  void	(*action) (void);
} operator;

static operator	**op_stack;
static size_t	  op_depth, max_op_depth;
static size_t	  parens;
static int	  op_expected;

Most of the operations expect one or two integers, and return their answer to the Top Of Stack. Other than do_divide() there's none that really needs checking (in real life no-one uses CCF's arithmetic capabilities), but it's nice to do it anyway. Multiplication's a bit tricky, though.

static long	 TOS, NOS;

static void do_plus	(void)
{
  if ((TOS > 0 && NOS > LONG_MAX - TOS) ||
      (TOS < 0 && NOS < LONG_MIN - TOS))
    FatalError ("arithmetic overflow");
  TOS = NOS + TOS;
}
static void do_minus	(void)
{
  if ((TOS < 0 && NOS > LONG_MAX + TOS) ||
      (TOS > 0 && NOS < LONG_MIN + TOS))
    FatalError ("arithmetic overflow");
  TOS = NOS - TOS;
}
static void do_times	(void)
{
  /*
    This is remarkably difficult to get right, so the following is
    only approximate.
  */
  if ((NOS > 0 && (TOS > LONG_MAX / NOS || TOS < LONG_MIN / NOS)) ||
      (NOS < 0 && (TOS < LONG_MAX / NOS || TOS > LONG_MIN / NOS)))
    FatalError ("arithmetic overflow");
  else
    TOS = NOS * TOS;
}
static void do_divide	(void)
{
  if (TOS == 0)
    FatalError ("divide by zero");
  TOS = NOS / TOS;
}
static void do_equal	(void) {TOS = NOS == TOS;}
static void do_notequal	(void) {TOS = NOS != TOS;}
static void do_lt	(void) {TOS = NOS < TOS;}
static void do_le	(void) {TOS = NOS <= TOS;}
static void do_gt	(void) {TOS = NOS > TOS;}
static void do_ge	(void) {TOS = NOS >= TOS;}
static void do_and	(void) {TOS = NOS && TOS;}
static void do_or	(void) {TOS = NOS || TOS;}


static operator binops [] =
{
  {"+",		5,	do_plus},
  {"-",		5,	do_minus},
  {"*",		6,	do_times},
  {"/",		6,	do_divide},
  {"=",		3,	do_equal},
  {"==",	3,	do_equal},
  {"!=",	3,	do_notequal},
  {"/=",	3,	do_notequal},
  {"<>",	3,	do_notequal},
  {"<",		4,	do_lt},
  {"<=",	4,	do_le},
  {">",		4,	do_gt},
  {">=",	4,	do_ge},
  {"and",	2,	do_and},
  {"or",	2,	do_or},
  {NULL,	0,	NULL}
};

static void do_negate	(void) {TOS = - TOS;}
static void do_not	(void) {TOS = ! TOS;}

static operator unops [] =
{
  {"-",		10,	do_negate},
  {"not",	10,	do_not},
  {NULL,	0,	NULL}
};


The single pseudo-function gets a table all to itself. This reflects the fact that it was an afterthought, and references are fudged in as variable accesses (see below). If I ever add more functions this approach will probably become unwieldy, so I might change it.

static void do_defined (void)
{
  TOS = IsDefined (token);
}

static operator magic_fns [] =
{
  {"ccf:defined",	0,	do_defined},
  {NULL,		0,	NULL}
};

static operator marker = {"(", 0, NULL};

static void push_op (operator *op)
{
  if (op_depth == max_op_depth)
  {
    max_op_depth += 10;
    op_stack = my_realloc (op_stack, max_op_depth * sizeof (*op_stack));
  }
  op_stack [op_depth++] = op;
}

static void push_int (long val)
{
  if (int_depth == max_int_depth)
  {
    max_int_depth += 10;
    int_stack = my_realloc (int_stack, max_int_depth * sizeof (long));
  }
  int_stack [int_depth++] = val;
}

static operator *find_op (operator *table)
{
  while (table -> name)
    if (strcmp (table -> name, token) == 0)
      return (table);
    else
      table += 1;
  return (NULL);
}

static void Syntax (void)
{
  FatalError ("syntax error in expression");
}


Here's where we evaluate a token - either a variable or an integer. Tucked away inside the "variable" bit, though, there's a check for any of the magic functions being called, together with the parens and arg checking. It looks quite tidy, considering what an ugly piece of design it is. Just wait until there are more functions, then you'll see the warts clearly.

#define MOST_DIGITS	(LONG_MAX / 10)
#define	LAST_DIGIT	((int) (LONG_MAX % 10))

static long token_value (void)
{
  if (isdigit (*token))
  {
    char *p = token;
    long val = *p++ - '0';
    while (isdigit (*p))
    {
      int digit = (*p++ - '0');
      if (val > MOST_DIGITS || (val == MOST_DIGITS && digit > LAST_DIGIT))
	FatalError ("constant too large");
      val = 10 * val + digit;
    }
    if (*p)
      Syntax ();
    return (val);
  }
  else
  {
    operator *op = find_op (magic_fns);
    if (op)
    {
      if (! (GetToken () && strcmp (token, "(") == 0))
        FatalPrintf (1, "missing ( after %s", op -> name);
      if (! GetToken ())
        FatalPrintf (1, "%s needs an operand", op -> name);
      op -> action ();
      if (! (GetToken () && strcmp (token, ")") == 0))
        FatalPrintf (1, "missing ) after %s", op -> name);
      return (TOS);
    }
    return (GetValue (token));
  }
}


Reducing an operation simply means to execute it here. Just go down the operator stack performing the implied action until we find something that's got lower priority than the one we're about to push.

static void reduce_ops (int priority)
{
  operator *top_op;
  while ((top_op = op_stack [op_depth - 1]) -> priority >= priority)
  {
    if (top_op -> priority < 10)
    {
      if (int_depth < 2)
        Syntax ();
      TOS = int_stack [--int_depth];
      NOS = int_stack [int_depth - 1];
    }
    else
    {
      if (int_depth < 1)
        Syntax ();
      TOS = int_stack [int_depth - 1];
    }
    top_op -> action ();
    int_stack [int_depth - 1] = TOS;
    op_depth -= 1;
  }
}


And so we come to the main routine for this module. Spin in a loop, accepting operators or numbers alternately, until the end of the line is reached.

Whenever we see an open parenthesis we push a low-priority pseudo-operator, which can never be reduced. When we see the matching close paren we check that the top of the operator stack is our marker and, if so, quietly remove it. It also makes life a little easier if we treat the start of the line as an open paren.

At the end, of course, we check there are no outstanding parens, and that we've got precisely one integer waiting to be returned.

long Evaluate (int then_allowed)
{
  operator *op = NULL;

  op_expected = 0;
  op_depth = 0;
  int_depth = 0;
  push_op (&marker);
  while (GetToken () &&
         ! (op_expected && then_allowed && strcmp (token, "then") == 0))
  {
    if (op_expected)
    {
      /* token should contain a binary operator or ')' */
      if (parens && strcmp (token, ")") == 0)
      {
        /* close paren */
	reduce_ops (1);
	if (op_depth < 2 || op_stack [--op_depth] != &marker)
	  Syntax ();
	parens -= 1;
      }
      else if ((op = find_op (binops)) != NULL)
      {
        /* maybe reduce something */
	reduce_ops (op -> priority);
	push_op (op);
	op_expected = 0;
      }
      else
        Syntax ();
    }
    else
    {
      /* token should be a '(', a unary operator, an integer, or a name */
      if (strcmp (token, "(") == 0)
      {
        parens += 1;
	push_op (&marker);
      }
      else if ((op = find_op (unops)) != NULL)
        push_op (op);
      else if ((op = find_op (binops)) != NULL)
        Syntax ();
      else
      {
        push_int (token_value ());
	op_expected = 1;
      }
    }
  }
  if (op_depth > 1)
    reduce_ops (1);

  if (op_depth != 1 || ! op_expected || parens || int_depth != 1)
    Syntax ();

  return (*int_stack);
}