/*
 * CCMP2C - C code macro preprocessor, generating C
 * Bruno Haible 22.6.1997
 */

/*
  This preprocessor takes a text file containing macros of the form
   ##define MACRONAME(macroarg,...)  expansion
 and generates some C code which prints the expansion.
 Example input:
   ##define DEFACCESSOR(clas,name)   \
     DEFREADER(clas,name)            \
     DEFWRITER(clas,name)
   ##define DEFREADER(clas,name)  \
     LISPFUNN(clas##_##name,2)    \
     { value1 = *get_slot_##clas##_##name(popSTACK()); }
   ##define DEFWRITER(clas,name)         \
     LISPFUNN(clas##_##name##_setter,3)  \
     { var object newvalue = popSTACK(); \
       value1 = *get_slot_##clas##_##name(popSTACK()) = newvalue1; }
   DEFACCESSOR(ship,x)
 Example output (modulo #line statements): something like that:
   static void emit_DEFACCESSOR(TEXT* clas, TEXT* name);
   static void emit_DEFREADER(TEXT* clas, TEXT* name);
   static void emit_DEFWRITER(TEXT* clas, TEXT* name);
   static void emit_DEFACCESSOR(TEXT* clas, TEXT* name) {
     emit("  "); emit_DEFREADER(clas,name); emit("           \n");
     emit("  "); emit_DEFWRITER(clas,name);
   }
   static void emit_DEFREADER(TEXT* clas, TEXT* name) {
     emit("  LISPFUNN("); emit(clas); emit("_"); emit(name); emit(",2) \n");
     emit("  { value1 = *get_slot_"); emit(clas); emit("_"); emit(name);
     emit("(popSTACK()); }");
   }
   static void emit_DEFWRITER(TEXT* clas, TEXT* name) {
     emit("  LISPFUNN("); emit(clas); emit("_"); emit(name);
     emit("_setter,3) \n");
     emit("  { var object newvalue = popSTACK(); \n");
     emit("    value1 = *get_slot_"); emit(clas); emit("_"); emit(name);
     emit("(popSTACK()) = newvalue1; }");
   }
   emit_DEFACCESSOR("ship","x"); emit("\n");
*/

typedef int  boolean_t;
#define FALSE 0
#define TRUE 1

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

#ifdef __cplusplus
extern "C" void exit(int);
#endif

/* maximum number of macro arguments */
#define MAXARGCOUNT        50
/* define as 1 if C++ style comments "//" shall be understood */
#define CPLUSPLUS_COMMENTS 1
/* define as 1 if comment5 style comments "# " shall be understood */
#define COMMENT5_COMMENTS  1
/* functions are split after this many statements */
#define MAXFUNCLENGTH  20

/* get a line, terminated with '\n', or NULL if EOF is encountered */
static unsigned char * get_line (FILE * fp) {
  int len = 1;
  unsigned char * line = (unsigned char *) malloc(len);
  int index = 0;
  while(1) {
    int c = fgetc(fp);
    if (c==EOF) { free(line); return (unsigned char *) 0; }
    if (index >= len) {
      len = 2*len;
      line = (unsigned char *) realloc((void*)line,len);
    }
    if (!line) { fprintf(stderr,"Out of memory.\n"); exit(1); }
    line[index++] = c;
    if (c=='\n') break;
  }
  return line;
}

/* simple strings */
typedef struct {
  unsigned long length;
  unsigned char * data;
} string_t;

/* string comparison, returns TRUE when they are equal (not like strcmp!) */
static boolean_t string_compare (string_t* s1, string_t* s2) {
  if (s1->length==s2->length) {
    unsigned long count = s1->length;
    unsigned char * p1 = s1->data;
    unsigned char * p2 = s2->data;
    while (count > 0) {
      if (*p1 != *p2) return FALSE;
      p1++; p2++; count--;
    }
    return TRUE;
  } else
    return FALSE;
}

/* A list of strings */
typedef struct {
  unsigned long index;
  unsigned long size;
  string_t* data;
} string_list_t;

static void stringlist_init (string_list_t* l) {
  l->data = (string_t*) malloc((l->size=1)*sizeof(string_t));
  if (!l->data) { fprintf(stderr,"Out of memory.\n"); exit(1); }
  l->index = 0;
}

static void stringlist_add (string_list_t* l, string_t* s) {
  if (l->index >= l->size) {
    l->data = (string_t*) realloc((void*)l->data,(l->size=2*l->size+1)
                                  *sizeof(string_t));
    if (!l->data) { fprintf(stderr,"Out of memory.\n"); exit(1); }
  }
  l->data[l->index++] = *s;
}

static boolean_t stringlist_lookup (string_list_t* l,string_t* s) {
  unsigned long i;
  for (i = l->index; i > 0; ) {
    i--;
    if (string_compare(s,&l->data[i])) return TRUE;
  }
  return FALSE;
}

/* The list of globally defined macros */
static string_list_t macronames;

/* A counter for temporary variable names */
static int gensym_count = 0;

/* During output, we split the code in many small functions, so that it's
   easier to compile. (Some C compilers have problems compiling functions
   with a lot of code and no if/for/do/while control structure because this
   function is a single huge "basic block".) */

/* highest number of function */
static int func_number = 0;
/* number of statements, -1 when func is closed */
static int func_statements = -1;
/* lines for main(), including the control lines */
static string_list_t main_output;

static void finish_func (void) {
  if (func_statements >= 0) { printf("}\n"); func_statements = -1; }
}

static void new_statement (void) {
  if (func_statements >= MAXFUNCLENGTH) { finish_func(); }
  if (func_statements < 0) {
    func_number++; func_statements = 0;
    printf("void main%d (TEXT* curr)\n",func_number);
    printf("{\n");
    { string_t main_line;
      main_line.data = (unsigned char *) malloc(30);
      if (!main_line.data) { fprintf(stderr,"Out of memory.\n"); exit(1); }
      sprintf((char*)main_line.data,"$  main%d(&curr);",func_number);
      main_line.length = strlen((char*)main_line.data);
      stringlist_add(&main_output,&main_line);
    }
  }
  func_statements++;
}

/* tests whether a line contains a ##if/##else/##endif directive */
static boolean_t control_line_p (unsigned char * line) {
  return (line[0]=='#' && line[1]=='#'
          && ((   line[2]=='i'
               && line[3]=='f'
               && ((line[4]==' ' || line[4]=='\t')
                   || (   line[4]=='d'
                       && line[5]=='e'
                       && line[6]=='f'
                       && (line[7]==' ' || line[7]=='\t'))
                   || (   line[4]=='n'
                       && line[5]=='d'
                       && line[6]=='e'
                       && line[7]=='f'
                       && (line[8]==' ' || line[8]=='\t'))))
                 || (   line[2]=='e'
                     && line[3]=='l'
                     && ((   line[4]=='s'
                          && line[5]=='e'
                          && (line[6]==' ' || line[6]=='\t' || line[6]=='\n'))
                         || (   line[4]=='i'
                             && line[5]=='f'
                             && (line[6]==' ' || line[6]=='\t'))))
                 || (   line[2]=='e'
                     && line[3]=='n'
                     && line[4]=='d'
                     && line[5]=='i'
                     && line[6]=='f'
                     && (line[7]==' ' || line[7]=='\t' || line[7]=='\n'))));
}

/* tests whether a line contains a macro definition */
static boolean_t define_line_p (unsigned char * line) {
  return (line[0]=='#' && line[1]=='#'
          && line[2]=='d'
          && line[3]=='e'
          && line[4]=='f'
          && line[5]=='i'
          && line[6]=='n'
          && line[7]=='e'
          && (line[8]==' ' || line[8]=='\t'));
}

/* reads the continuation lines of a macro definition line and returns
   a string containing the entire macro definition (with backslash-newline
   replaced by newline) */
static void whole_definition (unsigned char * firstline, FILE * infile,
                              string_t * result) {
  unsigned long size = 1;
  unsigned char * whole = (unsigned char *) malloc(size);
  unsigned long len = 0;
  unsigned char * line = firstline;
  unsigned long linelen;
  boolean_t backslashed;
  while(1) {
    if (!whole) { fprintf(stderr,"Out of memory.\n"); exit(1); }
    linelen = 0; while (line[linelen] != '\n') { linelen++; }
    backslashed = (linelen > 0 && line[linelen-1]=='\\');
    if (backslashed) linelen--;
    while (size < len + 1 + linelen) {
      whole = (unsigned char *) realloc((void*)whole,(size = 2*size+1));
      if (!whole) { fprintf(stderr,"Out of memory.\n"); exit(1); }
    }
    { unsigned char * ptr = &whole[len];
      if (len > 0) { *ptr++ = '\n'; len++; }
      { unsigned long i; for (i = 0; i < linelen; i++) ptr[i] = line[i]; }
      len += linelen;
    }
    free(line);
    if (!backslashed) break;
    line = get_line(infile);
    if (!line) {
      fprintf(stderr,"Encountered EOF during macro definition.\n");
      exit(1);
    }
  }
  result->data = whole; result->length = len;
}

/* tokenisation of a buffer */
typedef struct {
  unsigned char * buf;
  unsigned long position;
  unsigned long endindex;
} BUFFILE_t;

static int next_char (BUFFILE_t* infile) {
  if (infile->position == infile->endindex)
    return EOF;
  else
    return infile->buf[infile->position++];
}

static int peek_char (BUFFILE_t* infile) {
  if (infile->position == infile->endindex)
    return EOF;
  else
    return infile->buf[infile->position];
}

enum token_type_t {
  eof, eofcomment, eol, ident, number, charconst, stringconst, sep, expr
};

typedef struct {
  enum token_type_t type;
  unsigned long startindex; /* start index in buffer */
  unsigned long endindex; /* end index in buffer */
  unsigned char ch; /* Operator/Separator */
} token_t;

typedef struct {
  unsigned long index;
  unsigned long size;
  token_t* data;
} tokens_t;

static void tokens_init (tokens_t* tokens) {
  tokens->index = tokens->size = 0;
  tokens->data = (token_t*) malloc((tokens->size=1000)*sizeof(token_t));
}

static void tokens_del (tokens_t* tokens) {
  if (tokens->size > 0) free(tokens->data);
}

/* input the next token_t*:
 (Innerhalb von Präprozessor-Direktiven zählt Zeilenende als eigenes token_t*,
 und '#' leitet keine verschachtelte Präprozessor-Direktive ein.) */
static token_t* nexttoken (BUFFILE_t* infile, tokens_t* tokens,
                           boolean_t within_comment,
                           boolean_t within_prep_directive) {
  if (tokens->index >= tokens->size) {
    tokens->data =
      (tokens->size==0 ? (token_t*) malloc((tokens->size=1)*sizeof(token_t))
       : (token_t*) realloc((void*)tokens->data,(tokens->size=2*tokens->size+1)
                            *sizeof(token_t)));
    if (!tokens->data) { fprintf(stderr,"Out of memory.\n"); exit(1); }
  }
  { /* place for the next token_t* */
    token_t* token = &tokens->data[tokens->index];
    int c;
    if (within_comment) goto in_comment;
   restart:
    token = &tokens->data[tokens->index];
    token->startindex = infile->position;
    c = next_char(infile);
    switch (c) {
      case EOF: /* EOF */ token->type = eof; goto fertig;
      case ' ': case '\v': case '\t': goto restart;
      case '\n': /* EOL */
        if (within_prep_directive) {
          token->type = eol; goto fertig;
        } else
          goto restart;
      case '\\':
        if (peek_char(infile)=='\n') {
          next_char(infile); goto restart;
        } else
          goto separator;
      case '/':
        if (peek_char(infile) == '*') {
          next_char(infile);
         in_comment:
          while(1) {
            c = next_char(infile);
            if (c==EOF) {
              token->type = eofcomment; goto fertig;
              /* was: fflush(stdout); fprintf(stderr,"unterminated comment\n"); break; */
            }
            if ((c=='*') && (peek_char(infile)=='/'))
              { next_char(infile); break; }
          }
          goto restart;
        }
        else
#if CPLUSPLUS_COMMENTS
          if (peek_char(infile) == '/') {
            next_char(infile);
            do { c = next_char(infile); }
            while (c!=EOF && c!='\n');
            goto restart;
          } else
#endif
            goto separator;
      case '*':
        if (peek_char(infile) == '/') {
          fflush(stdout);
          fprintf(stderr,"end of comment without corresponding start of comment\n");
        }
        goto separator;
      case '#':
        if (within_prep_directive)
          goto separator;
        else
#if COMMENT5_COMMENTS
          if (peek_char(infile) == ' ') {
            next_char(infile);
            do { c = next_char(infile); }
            while (c!=EOF && c!='\n');
            goto restart;
          } else
#endif
            {
              while(1) {
                token_t* subtoken = nexttoken(infile,tokens,FALSE,TRUE);
                if ((subtoken->type == eof) || (subtoken->type == eofcomment)
                    || (subtoken->type == eol))
                  break;
              }
              goto restart;
            }
      case '.':
        c = peek_char(infile);
        if (!(((c>='0') && (c<='9')) || (c=='.'))) goto separator;
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
        while(1) {
          c = peek_char(infile);
          if (((c>='0') && (c<='9'))
              || ((c>='A') && (c<='Z')) || ((c>='a') && (c<='z'))
              || (c=='.'))
            next_char(infile);
          else
            break;
        }
        token->type = number; goto fertig;
      case '\'':                /* character constant */
        while(1) {
          c = next_char(infile);
          if (c==EOF) {
            fflush(stdout);
            fprintf(stderr,"unterminated character constant\n");
            break;
          }
          if (c=='\'') break;
          if (c=='\\') { c = next_char(infile); }
        }
        token->type = charconst; goto fertig;
      case '\"':                /* string constant */
        while(1) {
          c = next_char(infile);
          if (c==EOF) {
            fflush(stdout);
            fprintf(stderr,"unterminated string constant\n");
            break;
          }
          if (c=='\"') break;
          if (c=='\\') { c = next_char(infile); }
        }
        token->type = stringconst; goto fertig;
      case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
      case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
      case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
      case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
      case 'Y': case 'Z':
      case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
      case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
      case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
      case 's': case 't': case 'u': case 'v': case 'w': case 'x':
      case 'y': case 'z':
      case '_': /* Identifier. */
        while(1) {
          c = peek_char(infile);
          if (   ((c>='0') && (c<='9'))
              || ((c>='A') && (c<='Z')) || ((c>='a') && (c<='z'))
                 || (c=='_'))
            next_char(infile);
          else
            break;
        }
        token->type = ident; goto fertig;
      default:
      separator:
        token->type = sep; token->ch = c; goto fertig;
    }
   fertig:
    token->endindex = infile->position;
    tokens->index++;
    return token;
  }
}

/* tokenifies a buffer and returns the list of tokens */
static void tokenify (unsigned char * line, unsigned long startindex,
                      unsigned long endindex, boolean_t* within_comment,
                      boolean_t within_prep_directive, tokens_t* tokens) {
  BUFFILE_t infile;
  infile.buf = line; infile.position = startindex; infile.endindex = endindex;
  while(1) {
    token_t* next = nexttoken(&infile,tokens,*within_comment,
                              within_prep_directive);
    if (next->type==eof) { *within_comment = FALSE; break; }
    if (next->type==eofcomment) { *within_comment = TRUE; break; }
  }
  tokens->index--;
}

/* Emit an indentation. */
static void emit_indent (unsigned long indent)
{ while (indent > 0) { putchar(' '); indent--; } }

/* Emit a string. */
static void emit_string (string_t* s) {
  unsigned long i;
  for (i = 0; i < s->length; i++) putchar(s->data[i]);
}

/* Emits a string containing a control directive.
   (Skip the first characters and the newlines. Terminate with a newline.) */
static void emit_control_directive (string_t* s) {
  unsigned long i;
  for (i = 1; i < s->length; i++)
    if (s->data[i] != '\n')
      putchar(s->data[i]);
  putchar('\n');
}

/* Emit the code for generating a literal string. */
static void emit_literal (unsigned char * line, unsigned long startindex,
                          unsigned long endindex, char * textvar,
                          unsigned long indent) {
  unsigned long i;
 restart:
  if (startindex == endindex) return;
  emit_indent(indent);
  printf("TEXT_addstring(%s,\"",textvar);
  for (i = startindex; i < endindex; i++) {
    unsigned char ch = line[i];
    if (ch == '\\' || ch == '"') {
      putchar('\\'); putchar(ch);
    } else if (ch >= ' ' && ch != '\'') {
      putchar(ch);
    } else if (ch == '\n') {
      printf("\\n"); printf("\");\n"); startindex = i+1;
      goto restart;
    } else if (ch == '\t') {
      printf("\\t");
    } else if (ch == '\b') {
      printf("\\b");
    } else if (ch == '\r') {
      printf("\\r");
    } else if (ch == '\f') {
      printf("\\f");
    } else if (ch == '\v') {
      printf("\\v");
    } else {
      putchar('\\');
      putchar('0'+((ch/64)%8));
      putchar('0'+((ch/8)%8));
      putchar('0'+(ch%8));
    }
  }
  printf("\");\n");
}

/* Emit the code for generating a certain string. */
static void emit_expansion (unsigned char * line, unsigned long startindex,
                            unsigned long endindex, boolean_t* within_comment,
                            string_list_t* macroargs, char * textvar,
                            unsigned long indent) {
  tokens_t tokens;
  tokens_init(&tokens);
  /* Tokenify the buffer. */
  tokenify(line,startindex,endindex,within_comment,macroargs!=NULL,&tokens);
  /* "#" and "##" eat their surrounding spaces. */
  if (macroargs) {
    unsigned long tokindex;
    for (tokindex = 0; tokindex < tokens.index; tokindex++) {
      token_t* t = &tokens.data[tokindex];
      if (t->type == sep && t->ch == '#') {
        tokens.data[tokindex].startindex =
          (tokindex > 0 ? tokens.data[tokindex-1].endindex : startindex);
        tokens.data[tokindex].endindex =
          (tokindex < tokens.index-1 ? tokens.data[tokindex+1].startindex
           : endindex);
      }
    }
  }
  /* Process the tokens. Only some ident tokens are active,
     and "#" tokens are skipped. */
  {
    unsigned long currindex;
    unsigned long tokindex;
    currindex = startindex;
    for (tokindex = 0; tokindex < tokens.index; tokindex++) {
      token_t* t = &tokens.data[tokindex];
      if (t->type == sep && t->ch == '#' && macroargs) {
        emit_literal(line,currindex,t->startindex,textvar,indent);
        currindex = t->endindex;
      } else if (t->type == ident) {
        string_t id;
        id.length = t->endindex - t->startindex;
        id.data = &line[t->startindex];
        if (macroargs && stringlist_lookup(macroargs,&id)) {
          /* Emit a reference to one of the macro arguments. */
          emit_literal(line,currindex,t->startindex,textvar,indent);
          emit_indent(indent);
          printf("TEXT_addtext(%s,",textvar);
          printf("arg_"); emit_string(&id); printf(");\n");
          currindex = t->endindex;
        } else if (stringlist_lookup(&macronames,&id)
                   && tokindex < tokens.index-1
                   && tokens.data[tokindex+1].type == sep
                   && tokens.data[tokindex+1].ch == '(') {
          /* Emit a macro call. */
          unsigned long param_count = 0;
          struct param_t {
            unsigned long startindex; unsigned long endindex; char temp[12];
          } params[MAXARGCOUNT];
          emit_literal(line,currindex,t->startindex,textvar,indent);
          tokindex += 2;
          if (tokindex >= tokens.index) goto no_more_tokens;
          if (tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == ')') {
            /* Macro call without arguments */
            emit_indent(indent);
            printf("emit_"); emit_string(&id); printf("(%s);\n",textvar);
          } else { /* Macro call with arguments */
            while(1) {
              if (param_count == MAXARGCOUNT)
                { fprintf(stderr,"too many macro arguments\n"); exit(1); }
              gensym_count++;
              sprintf(params[param_count].temp,"&temp%04d",gensym_count);
              params[param_count].startindex = params[param_count].endindex =
                tokens.data[tokindex].startindex;
              { unsigned long paren_count = 0;
                while(1) {
                  if (tokens.data[tokindex].type == sep) {
                    if (tokens.data[tokindex].ch == '(') {
                      paren_count++;
                    } else if (tokens.data[tokindex].ch == ')') {
                      if (paren_count == 0) break; else paren_count--;
                    } else if (tokens.data[tokindex].ch == ',') {
                      if (paren_count == 0) break;
                    }
                  }
                  params[param_count].endindex =
                    tokens.data[tokindex].endindex;
                  tokindex++;
                  if (tokindex >= tokens.index) goto no_more_tokens;
                }
              }
              param_count++;
              if (tokens.data[tokindex].ch == ')') break;
              tokindex++;
              if (tokindex >= tokens.index) goto no_more_tokens;
            }
            if (FALSE) {
             no_more_tokens:
              fprintf(stderr,"unterminated macro call\n"); exit(1);
            }
            { unsigned long i;
              for (i = 0; i < param_count; i++) {
                boolean_t dummy = FALSE;
                emit_indent(indent); printf("{\n");
                indent += 2;
                emit_indent(indent); printf("TEXT %s;\n",&params[i].temp[1]);
                emit_indent(indent); printf("TEXT_init(%s);\n",params[i].temp);
                emit_expansion(line,params[i].startindex,params[i].endindex,
                               &dummy,macroargs,params[i].temp,indent);
              }
              emit_indent(indent);
              printf("emit_"); emit_string(&id); printf("(%s",textvar);
              for (i = 0; i < param_count; i++)
                { printf(",%s",params[i].temp); }
              printf(");\n");
              for (i = param_count; i > 0; ) {
                i--;
                emit_indent(indent); printf("TEXT_del(%s);\n",params[i].temp);
                indent -= 2;
                emit_indent(indent); printf("}\n");
              }
            }
          }
          currindex = tokens.data[tokindex].endindex;
        }
      }
    }
    emit_literal(line,currindex,endindex,textvar,indent);
  }
  tokens_del(&tokens);
}

int main (int argc, char* argv[]) {
  char* infilename;
  FILE* infile;
  if (argc != 2) {
    fprintf(stderr,"Usage: %s inputfile\n",argv[0]);
    exit(1);
  }
  infilename = argv[1];
  /* Emit prologue. */
  printf("#include <stdio.h>\n");
  printf("#include <stdlib.h>\n");
  printf("#include <string.h>\n");
  printf("#ifdef __cplusplus\n");
  printf("extern \"C\" void exit(int);\n");
  printf("#endif\n");
  printf("\n");
  printf("typedef struct { FILE* file; char* buf; int buflen; int len; } TEXT;\n");
  printf("static void TEXT_init1 (TEXT* text, FILE* file)\n");
  printf("{\n");
  printf("  text->file = file;\n");
  printf("}\n");
  printf("static void TEXT_init (TEXT* text)\n");
  printf("{\n");
  printf("  text->file = (FILE*)0;\n");
  printf("  text->buf = (char*)malloc(text->buflen=1);\n");
  printf("  if (!text->buf) { fprintf(stderr,\"Out of memory.\\n\"); exit(1); }\n");
  printf("  text->buf[text->len = 0] = '\\0';\n");
  printf("}\n");
  printf("static void TEXT_del (TEXT* text)\n");
  printf("{\n");
  printf("  if (!text->file) { free(text->buf); }\n");
  printf("}\n");
  printf("static void TEXT_addstring (TEXT* text, char* s)\n");
  printf("{\n");
  printf("  if (text->file)\n");
  printf("    { fprintf(text->file,\"%%s\",s); }\n");
  printf("  else\n");
  printf("    {\n");
  printf("      int slen = strlen(s);\n");
  printf("      int newlen = text->len + slen + 1;\n");
  printf("      if (newlen > text->buflen)\n");
  printf("        { text->buf = (char*)realloc(text->buf,text->buflen=2*newlen);\n");
  printf("          if (!text->buf) { fprintf(stderr,\"Out of memory.\\n\"); exit(1); }\n");
  printf("        }\n");
  printf("      strcpy(text->buf+text->len,s);\n");
  printf("      text->len += slen;\n");
  printf("    }\n");
  printf("}\n");
  printf("static void TEXT_addtext (TEXT* text, TEXT* s)\n");
  printf("{\n");
  printf("  if (s->file)\n");
  printf("    { abort(); }\n");
  printf("  else\n");
  printf("    { TEXT_addstring(text,s->buf); }\n");
  printf("}\n");
  /* Emit forward declarations. */
  stringlist_init(&macronames);
  {
    if ((infile = fopen(infilename,"r"))==NULL) { exit(1); }
    while (1) {
      unsigned char * line = get_line(infile);
      if (!line) break;
      if (define_line_p(line)) {
        string_t definition;
        boolean_t within_comment;
        tokens_t tokens;
        string_t id;
        unsigned long tokindex;
        string_list_t macroargs;
        whole_definition(line,infile,&definition);
        within_comment = FALSE;
        tokens_init(&tokens);
        tokenify(definition.data,9,definition.length,
                 &within_comment,TRUE,&tokens);
        if (tokens.index == 0)
          { fprintf(stderr,"macro name missing\n"); exit(1); }
        if (tokens.data[0].type != ident)
          { fprintf(stderr,"bad macro name\n"); exit(1); }
        id.length = tokens.data[0].endindex - tokens.data[0].startindex;
        id.data = &definition.data[tokens.data[0].startindex];
        stringlist_add(&macronames,&id);
        stringlist_init(&macroargs);
        tokindex = 1;
        if (!(tokindex < tokens.index
              && tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == '('))
          { fprintf(stderr,"macro parameter list missing\n"); exit(1); }
        while(1) {
          tokindex++;
          if (tokindex >= tokens.index)
            { fprintf(stderr,"unterminated macro parameter list\n"); exit(1); }
          if (tokindex==2 && tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == ')')
            break;
          if (tokens.data[tokindex].type != ident) {
            fprintf(stderr,"missing parameter name in macro parameter list\n");
            exit(1);
          }
          {
            string_t pid;
            pid.length = tokens.data[tokindex].endindex
              - tokens.data[tokindex].startindex;
            pid.data = &definition.data[tokens.data[tokindex].startindex];
            stringlist_add(&macroargs,&pid);
          }
          tokindex++;
          if (tokindex >= tokens.index)
            { fprintf(stderr,"unterminated macro parameter list\n"); exit(1); }
          if (tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == ')')
            break;
          if (!(tokens.data[tokindex].type == sep
                && tokens.data[tokindex].ch == ',')) {
            fprintf(stderr,"syntax error in macro parameter list\n");
            exit(1);
          }
        }
        tokens_del(&tokens);
        printf("static void emit_"); emit_string(&id); printf(" (TEXT* curr");
        { unsigned long i;
          for (i = 0; i < macroargs.index; i++)
            { printf(", TEXT* arg_"); emit_string(&macroargs.data[i]); }
        }
        printf(");\n");
      } else if (control_line_p(line)) {
        string_t definition;
        whole_definition(line,infile,&definition);
        emit_control_directive(&definition);
        free(definition.data);
      } else
        free(line);
    }
    fclose(infile);
  }
  /* Process file and emit most of the stuff. */
  stringlist_init(&main_output);
  {
    boolean_t within_comment = FALSE;
    if ((infile = fopen(infilename,"r"))==NULL) { exit(1); }
    while (1) {
      unsigned char * line = get_line(infile);
      if (!line) break;
      if (define_line_p(line)) {
        string_t definition;
        boolean_t dummy;
        tokens_t tokens;
        string_t id;
        unsigned long tokindex;
        string_list_t macroargs;
        unsigned long expansion_startindex;
        whole_definition(line,infile,&definition);
        dummy = FALSE;
        tokens_init(&tokens);
        tokenify(definition.data,9,definition.length,&dummy,TRUE,&tokens);
        if (tokens.index == 0)
          { fprintf(stderr,"macro name missing\n"); exit(1); }
        if (tokens.data[0].type != ident)
          { fprintf(stderr,"bad macro name\n"); exit(1); }
        id.length = tokens.data[0].endindex - tokens.data[0].startindex;
        id.data = &definition.data[tokens.data[0].startindex];
        stringlist_init(&macroargs);
        tokindex = 1;
        if (!(tokindex < tokens.index
              && tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == '('))
          { fprintf(stderr,"macro parameter list missing\n"); exit(1); }
        while(1) {
          tokindex++;
          if (tokindex >= tokens.index)
            { fprintf(stderr,"unterminated macro parameter list\n"); exit(1); }
          if (tokindex==2 && tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == ')')
            break;
          if (tokens.data[tokindex].type != ident) {
            fprintf(stderr,"missing parameter name in macro parameter list\n");
            exit(1);
          }
          {
            string_t pid;
            pid.length = tokens.data[tokindex].endindex
              - tokens.data[tokindex].startindex;
            pid.data = &definition.data[tokens.data[tokindex].startindex];
            stringlist_add(&macroargs,&pid);
          }
          tokindex++;
          if (tokindex >= tokens.index)
            { fprintf(stderr,"unterminated macro parameter list\n"); exit(1); }
          if (tokens.data[tokindex].type == sep
              && tokens.data[tokindex].ch == ')')
            break;
          if (!(tokens.data[tokindex].type == sep
                && tokens.data[tokindex].ch == ',')) {
            fprintf(stderr,"syntax error in macro parameter list\n");
            exit(1);
          }
        }
        tokindex++;
        expansion_startindex = (tokindex < tokens.index
                                ? tokens.data[tokindex].startindex
                                : definition.length);
        tokens_del(&tokens);
        finish_func();
        printf("static void emit_"); emit_string(&id); printf("(TEXT* curr");
        { unsigned long i;
          for (i = 0; i < macroargs.index; i++)
            { printf(", TEXT* arg_"); emit_string(&macroargs.data[i]); }
        }
        printf(")\n");
        printf("{\n");
        dummy = FALSE;
        emit_expansion(definition.data,expansion_startindex,
                       definition.length,&dummy,&macroargs,"curr",2);
        printf("}\n");
      } else if (control_line_p(line)) { /* Control directive. */
        string_t definition;
        whole_definition(line,infile,&definition);
        finish_func();
        emit_control_directive(&definition);
        stringlist_add(&main_output,&definition);
      } else { /* Normal text. */
        unsigned long linelen;
        linelen = 0; while (line[linelen] != '\n') { linelen++; }
        new_statement();
        emit_expansion (line,0,linelen+1,&within_comment,NULL,"curr",2);
        free(line);
      }
    }
    if (within_comment) {
      fflush(stdout);
      fprintf(stderr,"unterminated comment\n");
      exit(1);
    }
    fclose(infile);
  }
  finish_func();
  /* Emit main function. */
  printf("int main () {\n");
  printf("  TEXT curr;\n");
  printf("  TEXT_init1(&curr,stdout);\n");
  {
    unsigned long i;
    for (i = 0; i < main_output.index; i++)
      emit_control_directive(&main_output.data[i]);
  }
  printf("  if (ferror(stdout)) { exit(1); }\n");
  printf("  exit(0);\n");
  printf("}\n");
  /* Done. */
  if (ferror(stdout)) { exit(1); }
  exit(0);
}
