/*
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);
}