/*-----------------------------------------------------------------------*
   data.c - process data lines
   Copyright (C) 2003 Robert Maul

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library 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
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   $Log: data.c,v $
   Revision 1.1.1.1  2003/01/24 22:19:05  maulr


 *-----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "data.h"

extern FILE *fin;
extern FILE *fout;

extern char *arg(char *s, int n, char delim);
extern char *progname;
extern clear(char *buf, int buflen);

void data_line(char *buf, int line_count, char *filename);
void push(node_p ptr);


/*-----------------------------------------------------------------------*
 - function: make_node  
 - purpose: make a node_t struct 
 *-----------------------------------------------------------------------*/

node_t *make_node(int toktyp,
		  node_p data1,
		  node_p data2,
		  node_p data3,
		  node_p next)
{
  node_p p;
  p = NEW(node_t);
  p->toktyp = toktyp;
  p->data1 = data1;
  p->data2 = data2;
  p->data3 = data3;
  p->next = next;
  return p;
}  

/*-----------------------------------------------------------------------*
 - the top stack ptr is altered dynamically as various ifdefs are
 - created and visited.  it is needed to keep track of statements
 - contained in ifdefs, ifndef and elses.  it also provides
 - a scoping mechanism for define statements.  values in duplicate
 - define statements that are closer to the ifdef will override the values
 *-----------------------------------------------------------------------*/

/*-----------------------------------------------------------------------*
 - input file example:
 - 
 - my dear aunt sally
 - #define boring 1
 - #ifdef boring
 -    is boring
 - #else
 -    is not boring
 - #endif
 - and that's all
 - 
 - statment list example:
 -
 -             toktyp     data1,            data2, data3, next
 -             ------     -----             -----  -----  ----
 - root     -> STATEMENT, state1,               0,     0, next1;
 - state1   -> TEXT,      "my dear aunt sally", 0,     0, 0;
 - next1    -> STATEMENT, state2,               0,     0, next2;
 - state2   -> DEFINE,    "boring",            "1",    0, 0; 
 - next2    -> STATEMENT, state3,               0,     0, next3;
 - state3   -> IFDEF,     "boring",         ifdef,     0, else;
 - ifdef    -> STATEMENT, state4,               0,     0, 0;
 - state4   -> TEXT,      "is boring"           0,     0, 0;
 - else     -> STATEMENT, state5,               0,     0, 0;
 - state5   -> TEXT,      "is not boring",      0,     0, 0;
 - next3    -> STATEMENT, state6,               0,     0, 0;
 - state6   -> TEXT,      "and that's all",     0,     0, 0;
 - 
 - stack example: (while visiting #else)
 -
 - top      -> STACK,     next2,            else,    top, pre;
 - pre      -> STACK,     root,             next1,   pre, 0;
 - 
 - the stack contains a statement list for each level.
 - 
 - when interpreting the statement list we maintain the stack also.
 - 
 - when an ifdef of ifndef is encountered, each statement list in
 - top->next is visited and all DEFINE statements are examined until
 - one is found that contains the symbol matching in the ifdef
 - all statements are visited in the top->pre level and the last
 - one that is found is the one that is used.  if one is found
 - for that level then no more levels are searched.
 -
 - data3 is used in the stack as a flag to signal if printing is
 - turned on or not.
 - 
 *-----------------------------------------------------------------------*/

/*-----------------------------------------------------------------------*
 - function: symbol_defined
 - start at top->data1 (first statement at current level)
 - search for last define statement in current list with that symbol
 - if not found, go up the stack until it is found
 - if not found return 0, if found return value
 *-----------------------------------------------------------------------*/

symbol_defined(char *name) {
  node_p statement_ptr, stop_statement, define_ptr, stack_ptr;
  int found, value;
  char *tmp;

  found = value = 0;
  stack_ptr = top;
  while (stack_ptr) {
    statement_ptr = stack_ptr->data1;
    stop_statement = stack_ptr->data2; // don't search past current ifdef
    while (statement_ptr) {
      if (statement_ptr == stop_statement) 
	break;
      if (define_ptr = statement_ptr->data1) {
        if (define_ptr->toktyp == DEFINE) {
	  if (strcmp(define_ptr->data1, name) == 0) {
            found = 1;
            tmp = define_ptr->data2;
	    if (strcmp(define_ptr->data2, "1") == 0) {
	      value = 1;
            }
          }
        }
      }
      statement_ptr = statement_ptr->next;
    }
    if (found) // found at current level no need to search stack further
      break;
    stack_ptr = stack_ptr->next;
  } 
  return value;
}

/*-----------------------------------------------------------------------*
 - function: add
 - purpose: add new_ptr current list
 *-----------------------------------------------------------------------*/

add(node_p new_ptr) {
  cur->data1 = new_ptr;
  cur->next = make_node(STATEMENT, 0, 0, 0, 0);
  top->data2 = cur = cur->next;
}

/*-----------------------------------------------------------------------*
 - macro: add_ifdef
 - purpose: push ifdef statement onto stack
 *-----------------------------------------------------------------------*/

add_ifdef(int ifdef_type, char *name) {
  node_p tmp = make_node(STATEMENT, 0, 0, 0, 0); /* empty then list */
  cur->data1 = make_node(ifdef_type, (node_p) name, tmp, 0, 0); 
  push(cur);
  cur = tmp;
  if (symbol_defined(name)) {
    if (ifdef_type == IFDEF) {
      top->data3 = top; /* enable printing at this level */
    } else {
      top->data3 = 0; /* disable printing at this level */
    }
  } else {
    if (ifdef_type == IFNDEF) {
      top->data3 = top; /* enable printing at this level */
    } else {
      top->data3 = 0; /* disable printing at this level */
    }
  }
}

/*-----------------------------------------------------------------------*
 - macro: add_else
 - purpose: add else statement to current ifdef (always on top of stack)
 *-----------------------------------------------------------------------*/

add_else() {
  node_p ptr;
  if (top) {
    ptr = (node_p) top->data1; /* always toktyp == STATEMENT */
    ptr = (node_p) ptr->data1; /* always toktyp == IFDEF or IFNDEF */
    if ((ptr->toktyp == IFDEF) || (ptr->toktyp == IFNDEF)) {
      top->data2 = cur = ptr->next = make_node(STATEMENT, 0, 0, 0, 0);
      if (top->data3) {
       	top->data3 = 0;
      } else {
      	top->data3 = top;
      }
      return 0;
    }
  }    
  return 1; /* error */
}

/*-----------------------------------------------------------------------*
 - function: push
 - purpose: push pointer onto stack
 - (not sure if data3 pointer will ever be used)
 - 
 *-----------------------------------------------------------------------*/

void push(node_p ptr) {
  node_p tmp = make_node(STACK, ptr, ptr, 0, top);
  top = tmp;
  // dump_stack(top);
}

/*-----------------------------------------------------------------------*
 - function: pop
 - purpose: pop top of stack
 *-----------------------------------------------------------------------*/

node_p pop() {
  top = top->next;
  cur = top->data2;
  cur->next = make_node(STATEMENT, 0, 0, 0, 0);
  cur = cur->next;
  return top;
}

/*-----------------------------------------------------------------------*
 - function: include_file
 - purpose: open include file and process bytes as if they were part of
 -          the regular input processed in main
 *-----------------------------------------------------------------------*/

char* include_file(char *fn) {
  static char tmp[1000];
  char *incfilename;
  FILE *fp;
  char *buf;
  int ll, line_count, buflen, c;

  buflen = BUFLEN;
  buf = (char *) malloc(buflen+1);
  ll = line_count = 0;
  clear(tmp, 1000);
  strcpy(tmp, fn);
  incfilename = strtok(tmp, "\""); /* get rid of quotes */
  if (fp = fopen(incfilename,"r")) {
    while ((c = getc(fp)) != EOF) {
      if (ll >= buflen) {
        buflen += BUFINC;
        buf = (char *) realloc(buf, buflen+1);
      }
      buf[ll++] = c;
      if (c == '\n') {
        buf[ll-1] = '\0'; /* change newline char to NULL char */
        line_count++;
        ll = 0;
        data_line(buf, line_count, incfilename);
        clear(buf, buflen); /* init buf to NULL chars */
      }
    }
    fclose(fp);
    return NULL; /* read okay */
  }
  return incfilename;
}

/*-----------------------------------------------------------------------*
 - function: strtolower
 *-----------------------------------------------------------------------*/

void strtolower(char *str) {
  while (*str) {
    *str++ = tolower(*str);
  }
}

/*-----------------------------------------------------------------------*
 - function: printing_on
 - purpose: stack->data3 must be turned on all the way down the stack
 -          (up nesting level) to print
 *-----------------------------------------------------------------------*/

int printing_on(node_p ptr) {
  while (ptr) {
    if (!(ptr->data3)) 
      return 0;
    ptr = ptr->next;
  } 
  return 1;
}

/*-----------------------------------------------------------------------*
 - function: data_line
 *-----------------------------------------------------------------------*/

void data_line(char *buf, int line_count, char *filename) {
  char *arg1, *arg2, *arg3, *tmp, *incname;

  static char line_file[100];

  clear(line_file, 100);
  sprintf(line_file, "#line %d \"%s\"", line_count, filename);
  cur->data3 = strdup(line_file);
  tmp = strdup(buf);
  arg1 = strtok(tmp, " \t"); if (!arg1) arg1 = "";
  arg2 = strtok(0, " \t"); if (!arg2) arg2 = "";
  arg3 = strtok(0, " \t"); if (!arg3) arg3 = "";
  strtolower(arg1);
  if (strcmp(arg1, "#define") == 0) {
    add(make_node(DEFINE,
		  (node_p) strdup(arg2),
		  (node_p) strdup(arg3), 0, 0));
  } else if (strcmp(arg1, "#ifdef") == 0) {
    add_ifdef(IFDEF, strdup(arg2));
  } else if (strcmp(arg1, "#ifndef") == 0) {
    add_ifdef(IFNDEF, strdup(arg2));
  } else if (strcmp(arg1, "#endif") == 0) {
	  if (pop() == NULL) {
		fprintf(stderr, "error %s: line %d of %s, #endif has no #ifdef\n",
	      progname, line_count, filename);
	  }
  } else if (strcmp(arg1, "#else") == 0) {
    if (add_else()) {
      fprintf(stderr, "error %s: line %d of %s, #else has no #ifdef\n",
	      progname, line_count, filename);
    }
  } else if (strcmp(arg1, "#include") == 0) {
    if (incname = include_file(arg2)) {
      fprintf(stderr, "error %s: line %d of %s, problem opening file %s\n",
	      progname, line_count, filename, incname);
    }
  } else {
    if (printing_on(top)) {
      fprintf(fout, "%s\n", buf);
    }
    // add(make_node(TEXT, (node_p) strdup(buf), 0, 0, 0));
  }
  free(tmp);
}


