// TO DO: support for cpp should go here (#include, #define, #ifdef/#if etc)
// - currently using gcc's cpp.  (#line as passed through by cpp would be helpful)

// "string1" "string2" - concatenate in the lexer?

// To support source reconstruction, add an extra field to the c[] structs,
// which is the exact white space that was skipped before that token.

// hex constants (0x80).  octal constants (077).  long and long long literals (1L, 1LL)

// Need to add token for "&&"

char *escape(char *s, int t)
{
  static char result[1024];
  char *rslt;
  int quote;

  rslt = result;
  if ((t != B_character_constant) && (t != B_string_constant)) {
    rslt += sprintf(rslt, "%s", s); return result;
  }
  if (t == B_character_constant) quote = 39 /* '\'' */ ;
  if (t == B_string_constant) quote = '"';
  rslt += sprintf(rslt, "%c", quote);
  for (;;) {
    int c = *s++;
    if (c == '\n') rslt += sprintf(rslt, "\\n");
    else if (c == '\r') rslt += sprintf(rslt, "\\r");
    else if (c == '\t') rslt += sprintf(rslt, "\\t");
    else if ((c == 0) && (quote == 39 /* '\'' */ )) rslt += sprintf(rslt, "\\0");
    else if (c == '\\') rslt += sprintf(rslt, "\\\\");
    else if ((c == '"') && (quote == '"')) rslt += sprintf(rslt, "\\\"");
    else if ((c == 39) && (quote == 39)) rslt += sprintf(rslt, "\\'");
    else rslt += sprintf(rslt, "%c", c);
    if ((c == 0) || (quote == 39 /* '\'' */ )) break;
  }
  sprintf(result+strlen(result), "%c", quote);
  return result;
}

void dump_source(void)
{
  int l;
  int line = -1;
  for (l = 0; l < nextfree; l++) {
    if (c[l].t == TYPE_EOF) break;
    if (c[l].l != line) {
      fprintf(stdout, "\n");
      line = c[l].l;
    }
    fprintf(stdout, "%s", escape(c[l].s, c[l].t));
  }
  fprintf(stdout, "\n");
}

void error_line(int target)
{
  int l;
  fprintf(stderr, "Line %d: ", target);
  for (l = 0; l < nextfree; l++) {
    if (c[l].t == TYPE_EOF) break;
    if (c[l].l == target) {
      fprintf(stderr, "%s", escape(c[l].s, c[l].t));
    }
  }
  fprintf(stderr, "\n");
}

/* Support procs for storing lexical units */
void stores(char *s, int lineno, int col, int type, char *fname) {
  int tag;

  if (nextstring + strlen(s) + 1 >= MAXPOOL) exit(1); // TO DO: add message
  strcpy(stringpool+nextstring, s); /* Create a backstop for when not found */
  tag = str_to_pool(s);
  if (tag == nextstring) nextstring += strlen(s)+1; /* Not found, add it */

  c[nextfree].s = stringpool+tag; c[nextfree].l = lineno; c[nextfree].col = col;
  c[nextfree].f = fname; c[nextfree].t = type;
  //fprintf(stdout, "stores('%s')\n", c[nextfree].s);
  nextfree++;
}

void storec(int ch, int lineno, int col, int type, char *fname) {
  onecharstr[ch*2] = ch; onecharstr[ch*2+1] = '\0';  // convert char to 1-char string before saving.
  stores(&onecharstr[ch*2], lineno, col, type, fname);
}

int mkliteral(char *s)
{ // use in code generation but belongs in line reconstruction really - derived from 'stores'
  int tag;
  if (nextstring + strlen(s) + 1 >= MAXPOOL) exit(1); // TO DO: add message
  strcpy(stringpool+nextstring, s); // Create a backstop for when not found
  tag = str_to_pool(s);
  if (tag == nextstring) nextstring += strlen(s)+1; // Not found, add it
  c[nextfree].s = stringpool+tag; c[nextfree].l = -1; c[nextfree].col = -1;
  c[nextfree].f = ""; c[nextfree].t = B_integer_constant; // OOPS: 'mktag' comes through here too...
  //fprintf(stderr, "c[%d] = %s\n", nextfree, c[nextfree].s);
  return nextfree++;
}

/* simple proc to recognise if a token is a keyword.  Could use a hash if runtimes warrant it. */
/* (takeon.c could generate a perfect hash; or we could use a trie as in my spelling utils) */
int iskeyword(char *s) {
  int i;
  for (i = 0; i < MAX_KEYWORD; i++) if (strcmp(s, keyword[i]) == 0) return TRUE;
  return FALSE;
}


/*  Main procedures - the parser and the code generator (which embodies the grammar) */

/*  Line reconstruction, which for this language equates to lexing */
/*
    A C compiler always collects characters into the longest possible tokens,
  even if the result is not valid C. White space always divides tokens. White
  space can be used to prevent misinterpretation of C source code (for
  example, x--y would be tokenized as the illegal x -- y [combining the two
  hyphens into a single token], while x - -y would be toeknized as the valid
  x - - y). White space must be used to separate an identifier, reserved
  word, integer constant, floating point constant from a following
  identifier, reserved word, integer constant, or floating point constant.
  Although ANSI C requires that comments be replaced by white space, many
  compilers don.t, which can lead to unwanted token merging.  */
                             // -- from http://www.osdata.com/topic/language/c.htm
// ... so there's my answer:   john/**/smith is NOT the variable johnsmith, and
// my line reconstruction below has to take account of that.

// no word yet on whether a---b is legal (and equivalent to a-- - b) but
// I'm pretty sure it is.

static int xfgetc(FILE *f);
static void xungetc(int c, FILE *f);

char *wp; int wpstartcol;
void whitedump(char *fname, int col) {
  int tag;
  *wp = '\0';

  tag = str_to_pool(stringpool+nextstring);
  if (tag == nextstring) nextstring += strlen(stringpool+nextstring)+1;
  /* Not found, add it */
  c[nextfree].s = stringpool+tag; c[nextfree].l = lineno; c[nextfree].col = wpstartcol;
  c[nextfree].f = fname; c[nextfree].t = TYPE_WHITESPACE;
  //fprintf(stdout, "wd('%s')\n", c[nextfree].s);
  nextfree++;

  wp = stringpool+nextstring; wpstartcol = col;
}


void line_reconstruction(void)
{
  /*  Pre-process input ready for parsing.  Tokens are stored in array C[] */
  wp = stringpool+nextstring;  // add whitespace by *wp++ = ...
  wpstartcol = col;
  for (;;) {
    ch = xfgetc(sourcefile); if (ch == EOF) break;
    ch &= 255; // int, positive.

    peek = xfgetc(sourcefile); xungetc(peek, sourcefile);

    if (isalpha(ch) || (ch == '_')) {
        /*  token or keyword */
        int nextfree = 0, startcol = col;
        char token[4*1024];

        whitespace = FALSE;
        for (;;) {
                                               // better to use freespace at end of
                                               // stringpool than 'token'???
          if (isalpha(ch) || isdigit(ch) || (ch == '_')) {
            // digits and '_' allowed
            col++;
            token[nextfree++] = ch;
          } else {
            token[nextfree] = '\0'; xungetc(ch, sourcefile);
            break;
          }
          ch = xfgetc(sourcefile);
        }
        stores(token, lineno, startcol, iskeyword(token) ? TYPE_KEYWORD : B_TAG, curfile);
        //free(token);
        
    } else if (isdigit(ch)) {
        /*  Number */
        int nextfree = 0;
        char number[4*1024]; // again, use same trick as whitespace

        // Store as a string...
        whitespace = FALSE;
        for (;;) {
          if (isdigit(ch)) {
            col++;
            number[nextfree++] = ch;
          } else {
            number[nextfree] = '\0'; xungetc(ch, sourcefile);
            break;
          }
          ch = xfgetc(sourcefile);
        }
        stores(number, lineno, col, B_integer_constant, curfile);
        //free(number);
	
    } else switch (ch) {

    case '"':  // Handle "cc" string constants
    case 39:   // 39 /* '\'' */ : pcc has a bug and won't accept this literal!  // Handle 'c' character constants
      /*  literals */
      {
        int nextfree = 0, quotech = ch;
        char string[4*1024]; // again, whitespace trick would be better

        whitespace = FALSE;
        col++;
        for (;;) {
          ch = xfgetc(sourcefile); // Newlines are allowed
          col++;
	  if (ch == quotech) {
            string[nextfree] = '\0';
            break;
	  } else if (ch == '\\') {
            ch = xfgetc(sourcefile); col++;
            if (ch == '\\') {           string[nextfree++] = ch;
            } else if (ch == 39 /* '\'' */ ) {    string[nextfree++] = 39 /* '\'' */ ;
            } else if (ch == '"') {     string[nextfree++] = '"';
            } else if (ch == 'n') {     string[nextfree++] = '\n';
            } else if (ch == 'r') {     string[nextfree++] = '\r';
            } else if (ch == 't') {     string[nextfree++] = '\t';
            } else if (ch == '0') {     string[nextfree++] = '\0';
            } else {
              // Warn of unknown (to me) \x escape.  Probably an error.
	      string[nextfree++] = '\\'; string[nextfree++] = ch;
              fprintf(stderr, "? Warning: unknown escape \\%c at line %d\n", ch, lineno);
            }
          } else {
            string[nextfree++] = ch;
          }
        }
        stores(string, lineno, col, (quotech == 39 /* '\'' */  ? B_character_constant : B_string_constant), curfile);
        //free(string);
      }
      break;
      

    case '/':
      /*  COMMENTS (or just a divide symbol)
          Note the ambiguity here:   int a; int b; int c; int *d; a=b/c; *d=b/c; c=b/*d;
       */
      wpstartcol = col;
      col++;
      whitespace = FALSE;
      if (peek == '/') {
        // Handle line comment   TO DO: STORE WHITESPACE
        wp = stringpool+nextstring;  // add whitespace by *wp++ = ...
        *wp++ = peek;
        do {*wp++ = (ch = xfgetc(sourcefile));} while (ch != '\n');
        wp -= 1; // drop \n
        col = 0; whitespace = TRUE;
        whitedump(curfile, col); // dump at end of whitespace, *and* on end-of-line during whitespace
        lineno++;
      } else if (peek == '*') {
        /* Handle potential multi-line comment */
        wp = stringpool+nextstring;  // add whitespace by *wp++ = ...
        *wp++ = '/';
        *wp++ = (ch = xfgetc(sourcefile)); // Now we have read '/*'
        for (;;) { // TO DO: STORE WHITESPACE
          col++;
          *wp++ = (ch = xfgetc(sourcefile)); peek = xfgetc(sourcefile);
          if (ch == '\n') {
            col = 0; lineno++;
            whitedump(curfile, col); // part of comment from next line goes into following token
	  }
          if ((ch == '*') && (peek == '/')) break;
          xungetc(peek, sourcefile);
        }
        xungetc(peek, sourcefile);
        col += 2;
        *wp++ = '/'; (void)xfgetc(sourcefile); // Remove '/'
        peek = xfgetc(sourcefile); xungetc(peek, sourcefile);
        // QUESTION: How does this affect # directives? 
        whitedump(curfile, col); // dump at end of whitespace, *and* on end-of-line during whitespace
      } else {
        storec(ch, lineno, col, TYPE_CHAR, curfile);
      }
      break;

    case '+':  
    case '-':
      // ++ and -- are the only symbols that require disambiguation
      // at the lexer level in order to work with a grammar where
      // inter-token spaces have already been removed.  
      // TO DO: Add '&&'.  Currently hacked in the grammar.
      whitespace = FALSE;
      if (peek == ch) {
        // get second ch
        ch = xfgetc(sourcefile);
        peek = xfgetc(sourcefile); xungetc(peek, sourcefile);
        if (ch == '+')
          stores("++", lineno, col, TYPE_PPP, curfile);
        else
          stores("--", lineno, col, TYPE_MMM, curfile);
        col++;
      } else storec(ch, lineno, col, TYPE_CHAR, curfile);
      col++;
      break;

    // WHITESPACE  TO DO: STORE WHITESPACE
    case '\n':      lineno++;
    case '\r':      startline = TRUE; col = 0; whitespace = TRUE;
      wp = stringpool+nextstring;  // add whitespace by *wp++ = ...
      wpstartcol = col;
      // *wp++ = ch; drop \n
      whitedump(curfile, col);
      break;

    case '\t':
    case ' ':       col++; // Does not affect whitespace (why not?)
      wp = stringpool+nextstring;  // add whitespace by *wp++ = ...
      wpstartcol = col-1;
      *wp++ = ch;
      whitedump(curfile, col);
      break;

    default:
      whitespace = FALSE;
      storec(ch, lineno, col++, TYPE_CHAR, curfile);
    }
  }
  // set up a dummy at the end because we sometimes look ahead by 1
  // in the parsing code and don't want to hit uninitialised data.
  stores("<EOF>", lineno, col, TYPE_EOF, curfile); nextfree--;
}


static char xline[1024]; // make off heap instead, shorten binary
static int xi = 0, xcur_line = 1;

static void xungetc(int c, FILE *f)
{
    if (xi > 0) {
      xi -= 1;
    } else if (c == '\n') {
      xcur_line -= 1;
      xi = strlen(xline);
    }
    ungetc(c, f);
}

static int xfgetc(FILE *f)
{
  static int last_line = -1;
  int c;
  int ch;

  c = fgetc(f);
  if (c == EOF) return EOF;
  ch = c&255;
  if (ch == '\n') {
    xline[xi] = '\0'; xi = 0;
    if (last_line != xcur_line) {
      // so long sibce I wrote this I've forgotten how it worked (if it did) and
      // am a bit dubious about a clash here from the new code I'm now adding to
      // handle whitespace...
      //strcpy(stringpool+nextstring, xline); //(void)make_binary_tuple(LINE, xcur_line, str_to_pool(xline));
      last_line = xcur_line;
    }
    xcur_line += 1;
  } else xline[xi++] = ch;
  if (xi == 1023) xi = 1022;
  xline[xi] = '\0';
  return c;
}

