/*

   cpp-like preprocessor.  Implemements #define, #if, #ifdef, #else, #endif.
   Does *not* do any macro substitution.
   #define takes a simple integer, #if takes an integer expression.
   The integer expression correctly applies operator precedence rules.

   TO DO: #ifdef, #include "filename", and nested #if structures.

 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#ifndef FALSE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif

static int tpp_if = FALSE;   // are we in an if section?
static int tpp_else = FALSE; // are we in an else?
static int tpp_cond = FALSE; // was the last #if test true?
static int tpp_val = 0;      // last expr value
static int lineno = 1;

static char *token[128];
static int   value[128];
static int   nextfreetok = 0;

static void tpp_set(FILE *source)
{
  // read a var name and a value then \n
  // eg #define test 42
  char tok[80];
  char *s = tok;
  int i, c, rc;
  for (;;) {
    c = fgetc(source);
    if (!isalpha(c)) break;
    *s++ = c;
  }
  *s = '\0';
  token[nextfreetok] = strdup(tok);
  for (i = 0; i <= nextfreetok; i++) {
    if (strcmp(tok, token[i]) == 0) {
      if (i == nextfreetok) nextfreetok++; else
      fprintf(stderr, "error: redefinition of %s\n", tok);
      break;
    }
  }
  rc = fscanf(source, "%d\n", &value[i]);
  if (rc != 1) {
    fprintf(stderr, "error: malformed #define %s\n", tok); exit(1);
  }
}

static void skipspace(FILE *source)
{
  int c;
  for (;;) {
    c = fgetc(source);
    if (c != ' ' && c != '\t') break;
  }
  ungetc(c, source); // pascal-style lookahead...
}

static void tpp_test(FILE *source)
{
  int rc, i, c, top = 0, stackp = 0; // top is inclusive
  int nest[100]; // nest[0] is never used.
  int input_stream[100]; // operand stack.  nest[top] points to first operand after last '('
                         // stackp is exclusive and points to next free entry

  void insert_opener(void)
  {
    top += 1; nest[top] = stackp;
  }
  void insert_closer(void)
  {
      if (nest[top] == 0 && stackp == 0) {
        if (top <= 0) {fprintf(stderr, "internal error: top <= 0 on ')'\n"); exit(1);}
        else top -= 1;
      } else if (stackp-3 == nest[top]) {
        // replace a op b in input_stream with the result of the calculation.
        switch (input_stream[stackp-2]) {
        case '+': input_stream[stackp-3] = input_stream[stackp-3] + input_stream[stackp-1];  break;
        case '-': input_stream[stackp-3] = input_stream[stackp-3] - input_stream[stackp-1];  break;
        case '*': input_stream[stackp-3] = input_stream[stackp-3] * input_stream[stackp-1];  break;
        case '/': input_stream[stackp-3] = input_stream[stackp-3] / input_stream[stackp-1];  break;
        case '<': input_stream[stackp-3] = input_stream[stackp-3] < input_stream[stackp-1];  break;
        case '{': input_stream[stackp-3] = input_stream[stackp-3] <= input_stream[stackp-1];  break;
        case '>': input_stream[stackp-3] = input_stream[stackp-3] > input_stream[stackp-1];  break;
        case '}': input_stream[stackp-3] = input_stream[stackp-3] >= input_stream[stackp-1];  break;
        case '=': input_stream[stackp-3] = input_stream[stackp-3] == input_stream[stackp-1];  break;
        case '#': input_stream[stackp-3] = input_stream[stackp-3] != input_stream[stackp-1];  break;
        case '&': input_stream[stackp-3] = input_stream[stackp-3] && input_stream[stackp-1];  break;
        case '|': input_stream[stackp-3] = input_stream[stackp-3] || input_stream[stackp-1];  break;
        }
        stackp = nest[top]+1; top -= 1;
      } else if (stackp-1 == nest[top]) {
        // replace (a) with a  (actually just tweak nest)
        // ASSERT: stackp == nest[top]; // replace input_stream[stackp] with contents of bracketed expr...
        top -= 1;
      } else {fprintf(stderr, "internal error: stackp = %d, nest[%d] = %d\n", stackp, top, nest[top]); exit(1);}
      if (top < 0) {fprintf(stderr, "error: spurious ')' in #if expression\n"); exit(1);}
      tpp_cond = ((tpp_val = input_stream[stackp-1]) != 0);
  }
  void insert(char *s)
  {
    int c;
    while ((c = *s++) != '\0') {
      if (c == '(') insert_opener();
      else if (c == ')') insert_closer();
      else input_stream[stackp++] = c; // operator...
    }
  }

  // read expression, set tpp_cond = (expr) != 0
  tpp_cond = TRUE; nest[0] = 0;

  insert("((((((");
  for (;;) {
    skipspace(source);
    c = fgetc(source);
    if (c == '\n') {
      // either evaluate, or give a syntax error
      insert("))))))");
      if (top != 0) {fprintf(stderr, "error: unbalanced parentheses\n"); exit(1);}
      break;
    } else if (isdigit(c)) {
      ungetc(c, source);
      rc = fscanf(source, "%d", &i); // assert rc = 1
      input_stream[stackp++] = i;
    } else if (isalpha(c)) {
      // token
      char tok[80];
      char *s = tok;
      int i, rc;
      for (;;) {
        if (!isalpha(c)) break;
        *s++ = c;
        c = fgetc(source);
      }
      *s = '\0';
      ungetc(c, source);
      token[nextfreetok] = strdup(tok);
      for (i = 0; i <= nextfreetok; i++) {
        if (strcmp(tok, token[i]) == 0) {
          if (i == nextfreetok) {
            fprintf(stderr, "error: %s not set\n", tok); exit(1);
          }
          break;
        }
      }
      input_stream[stackp++] = value[i];
    } else {
    int c2 = fgetc(source); ungetc(c2, source);
    if (c == '(') { insert_opener();
    } else if (c == ')') { insert_closer();
    } else if (c == '*') { insert(")*(");
    } else if (c == '/') { insert(")/(");
    } else if (c == '+') { insert("))+((");
    } else if (c == '-') { insert("))-((");
    } else if (c == '<') { if (c2 == '=') {insert("))){((("); (void)fgetc(source);} else insert(")))<(((");
    } else if (c == '>') { if (c2 == '=') {insert(")))}((("); (void)fgetc(source);} else insert(")))>(((");
    } else if (c == '=' && c2 == '=') { insert(")))=((("); (void)fgetc(source);
    } else if (c == '!' && c2 == '=') { insert(")))#((("); (void)fgetc(source);
    } else if (c == '&' && c2 == '&') { insert("))))&(((("); (void)fgetc(source);
    } else if (c == '|' && c2 == '|') { insert(")))))|((((("); (void)fgetc(source);
    } else {
      fprintf(stderr, "error: invalid symbol '%c' in #if expression\n", c);
      exit(1);
    }
    }
  }
}

int main(int argc, char **argv)
{
  FILE *source = fopen(argv[1], "r");
  if (argc != 2) {fprintf(stderr, "syntax: %s file\n", argv[0]); exit(1);}
  if (source == NULL) {fprintf(stderr, "%s: %s\n", argv[0], strerror(errno)); exit(1);}
  for (;;) {
    int c = fgetc(source);
    if (c == EOF) break;
    else if (c == '#') {// No leading spaces or tabs allowed
      // we have a pre-processor line
      char tok[80];
      char *s = tok;
      for (;;) {
        c = fgetc(source);
        if (!isalpha(c)) break;
        *s++ = c;
      }
      *s = '\0';
      if (strcmp(tok, "define") == 0) {
        tpp_set(source);
      } else if (strcmp(tok, "if") == 0) {
        if (tpp_if || tpp_else) {fprintf(stderr, "Nested #if at line ?\n", lineno); exit(1);}
        // first hack does not allow nested constructs. Add later with a stack.  Here and for ifdef
        tpp_test(source);
        tpp_if = TRUE; tpp_else = FALSE;
      } else if (strcmp(tok, "ifdef") == 0) {
        if (tpp_if || tpp_else) {fprintf(stderr, "Nested #if at line ?\n", lineno); exit(1);}
        // tpp_test(source);
	// not yet implemented ************************************************************************
	tpp_cond = FALSE; // defaulting to 'not defined' for now
        tpp_if = TRUE; tpp_else = FALSE;
      } else if (strcmp(tok, "debug") == 0) {
        tpp_test(source);
        fprintf(stderr, "debug at line %d: %d\n", lineno, tpp_val);
      } else if (strcmp(tok, "else") == 0) {
        if (!tpp_if) {fprintf(stderr, "Bad #else at line ?\n", lineno); exit(1);}
        tpp_if = FALSE; tpp_else = TRUE;
      } else if (strcmp(tok, "endif") == 0) {
        tpp_if = FALSE; tpp_else = FALSE;
      } else {
        fprintf(stderr, "Invalid pre-processor line:\n");
        fprintf(stderr, "#%s", tok);
        if (c != '\n') for (;;) {
          fputc(c, stderr);
          c = fgetc(source);
          if (c == '\n') break;
        }
        fputc(c, stderr);
      }
    } else {
      for (;;) {
        if ((tpp_if && tpp_cond) ||
            (tpp_else && !tpp_cond) ||
            !(tpp_if || tpp_else)) fputc(c, stdout);
        if (c == '\n') break;
        c = fgetc(source);
      }
    }
  }
  exit(0);
  return(0);
}