home *** CD-ROM | disk | FTP | other *** search
- /*
- C* preprocessor -- directives
-
- source: dir.c
- started: October 7, 1985
- version:
- August 8, 1986
- February 20, 1989: added multiple path support to pp_include
- March 8, 1989
-
- PUBLIC DOMAIN SOFTWARE
-
- The CSTAR program was placed in the public domain on June 15, 1991,
- by its author and sole owner,
-
- Edward K. Ream
- 1617 Monroe Street
- Madison, WI 53711
- (608) 257-0802
-
- CSTAR may be used for any commercial or non-commercial purpose.
-
- See cstar.h or cstar.c for a DISCLAIMER OF WARRANTIES.
- */
- #include "cstar.h"
-
- /*
- Externally visible routines.
- */
- void do_pp(void);
-
- /*
- Internal routines.
- */
- static int eval (void);
- static int prec (int operator);
- static int gt_prec (int opr1, int opr2);
- static bool isfnch (int c, int delim);
- static int pop_op (void);
- static int pop_val (void);
- static void psh_op (int op);
- static void push_val (int val);
- static void pp_else (void);
- static void pp_endif (void);
- static void pp_enum (void);
- static void pp_if (void);
- static void pp_ifdef (bool flag);
- static void pp_incl (void);
- static void pp_line (void);
- static void pp_undef (void);
- static void skip_lines (void);
-
- /*
- The pp_def() routine, which handles #define directives, replaces the
- i'th formal argument in the replacement string by the ARG_FLAG
- character followed by i + ARG_OFFSET. The pp_expand() routine, which
- handles macro calls, replaces these two characters by the i'th actual
- argument.
-
- The ARG_FLAG character should be a character that can never appear in
- normal text, but it MUST NOT BE NEGATIVE, so as to fit in a char.
- */
-
- #define ARG_FLAG '$'
- #define ARG_OFFSET '0'
-
-
- /*
- Do one preprocessor directive.
- */
-
- #define EQL(string) str_eq(t_symbol+1,string+1)
-
- void
- do_pp(void)
- {
- char msg[100];
-
- TICK("do_pp");
-
- /* Get the directive into t_symbol[]. */
- if (!isalpha(ch)) {
- goto not_alpha;
- }
- t_id(t_symbol);
-
- /* Skip blanks after the directive. */
- skip_bl();
-
- switch(t_symbol [0]) {
-
- case 'd':
- if (t_length == 6 && EQL("define")) {
- pp_def();
- return;
- }
- goto not_pp;
-
- case 'e':
- if (t_length == 4 && EQL("else")) {
- pp_else();
- return;
- }
- if (t_length == 4 && EQL("enum")) {
- pp_enum();
- return;
- }
- else if (t_length == 5 && EQL("endif")) {
- pp_endif();
- return;
- }
- goto not_pp;
-
- case 'i':
- switch(t_length) {
- case 2: if (EQL("if")) {
- pp_if();
- return;
- }
- goto not_pp;
- case 5: if (EQL("ifdef")) {
- pp_ifdef(TRUE);
- return;
- }
- goto not_pp;
- case 6: if (EQL("ifndef")) {
- pp_ifdef(FALSE);
- return;
- }
- goto not_pp;
- case 7: if (EQL("include")) {
- pp_incl();
- return;
- }
- goto not_pp;
- }
- goto not_pp;
-
- case 'l':
- if (t_length == 4 && EQL("line")) {
- pp_line();
- return;
- }
- goto not_pp;
-
- case 'p':
- if (t_length == 6 && EQL("pragma")) {
- /* Do NOTHING!! */
- skip_pp();
- return;
- }
- goto not_pp;
-
- case 'u':
- if (t_length == 5 && EQL("undef")) {
- pp_undef();
- return;
- }
- goto not_pp;
-
- default:
- goto not_pp;
- }
-
- not_alpha:
- /*
- Be more permissive than the new C standard.
- Just skip the rest of the line.
- */
- skip_pp();
- return;
-
- not_pp:
- strcpy(msg, t_symbol);
- strcat(msg, " is not a valid preprocessor directive.");
- t_error(msg);
- skip_pp();
- }
-
- #undef EQL
-
-
- /*
- Define a list of constants.
- #enum(constant, id1, id2, ... , idn)
- */
- static void
- pp_enum(void)
- {
- register int flag;
- register long val;
- char symbol [100];
- char txt [LONG_DIGITS];
-
- TICK("pp_enum");
-
- /* Get opening '(' */
- skip_ws();
- if (ch == '(') {
- sysnext();
- }
- else {
- goto bad_ch;
- }
-
- /* Get initial constant. */
- if (ch < '0' || ch > '9') {
- goto bad_ch;
- }
- (void) t_number();
- val = t_value;
-
- flag = FALSE;
- for (;;) {
-
- skip_ws();
- if (ch == ')' ) {
- skip_pp();
- return;
- }
- if (ch == ',') {
- sysnext();
- if (flag == TRUE) {
- val++;
- }
- flag = TRUE;
- continue;
- }
-
- if (ch == '\\') {
- sysnext();
- if (ch == '\r') {
- sysnext();
- }
- if (ch == '\n') {
- sysnext();
- do_nl();
- begin_line(FALSE);
- continue;
- }
- else {
- goto bad_ch;
- }
- }
-
- if (flag == FALSE || !isid1(ch)) {
- goto bad_ch;
- }
-
- /* Get the symbol into symbol[]. */
- t_id(symbol);
-
- /* Look for '=' constant. */
- skip_bl();
- if (ch == '=') {
- sysnext();
- skip_ws();
-
- /* Get constant into val. */
- if (ch < '0' || ch > '9') {
- goto bad_ch;
- }
- (void) t_number();
- val = t_value;
- }
-
- /* Enter the symbol with no arguments. */
- convl2s(val, txt);
- (void) mst_enter(symbol, txt, -1);
-
- /* Bump val and indicate a comma is expected. */
- val++;
- flag = FALSE;
- }
-
- bad_ch: t_error("ill formed #enum");
- skip_pp();
- }
-
-
- /*
- Evaluate a constant expression to either true or false.
- A constant expression consists of:
-
- 1. integer constants or character constants
- 2. the unary - + and ~ operators
- 3. the binary + - * / & | ^ << >> == != < > <= >= oprators
- 4. the ternary ? : operator
-
- Identifiers are expanded if they are defined, otherwise they
- are taken to have a value of zero.
-
- The code below evaluates constant expressions using an operator
- precedence algorithm which employs an operator stack and an
- operand stack. The global t_evalstk[] array houses both stacks.
- The operator stack starts at t_evalstk[0] and grows up.
- The operand stack starts at t_evalstk[MAX_EVAL-1] and grows down.
- The globals t_opptr and t_valptr are point at the top of each stack.
- As usual, stack overflow happens when these two pointers "cross."
- Several auxilliary routines are used to handle these stacks:
- psh_op(), pop_op(), push_val() and pop_val();
- */
-
- static int
- eval(void)
- {
- register int op, op2, token, val1, val2, val3;
-
- TICK("eval");
-
- /* Initialize the operator and operand stacks. */
- t_opptr = -1;
- t_valptr = MAX_EVAL;
-
-
- /* State S1: unary +, unary -, ~, constant or id is expected here. */
-
- s1:
- token = con_token();
-
- /* Push all unary ops. */
- if (token == PLUS_TOK) {
- psh_op(UPLUS_TOK);
- goto s1;
- }
- else if (token == MINUS_TOK) {
- psh_op(UMINUS_TOK);
- goto s1;
- }
- else if (token == TILDE_TOK) {
- psh_op(TILDE_TOK);
- goto s1;
- }
-
- /* We expect a constant or identifier here. */
- if (token == INT_TOK || token == CHAR_TOK) {
- push_val((int) t_value);
- }
- else if (token == ID_TOK) {
- push_val(0);
- }
- else {
- goto bad_expr;
- }
-
- /* Perform all unary ops and enter state S2. */
- while (t_opptr >= 0) {
- switch (op = pop_op()) {
- case UPLUS_TOK: break;
- case UMINUS_TOK: push_val(-pop_val()); break;
- case TILDE_TOK: push_val(~pop_val()); break;
- default: psh_op(op); goto s2;
- }
- }
-
- /* State S2: binary op or end_of_expression expected here. */
-
- s2:
- token = con_token();
-
- /*
- Perform binary operators until the operator stack is
- empty or until token operator has a higher precedence
- than the operator on the top of the operator stack.
- */
-
- while (t_opptr >= 0 && gt_prec(t_evalstk[t_opptr], token)) {
-
- val2 = pop_val();
- val1 = pop_val();
- op = pop_op();
-
- switch (op) {
- case PLUS_TOK: push_val(val1 + val2); break;
- case MINUS_TOK: push_val(val1 - val2); break;
- case STAR_TOK: push_val(val1 * val2); break;
- case DIV_TOK: push_val(val2 ? (val1/val2) : 0);
- break;
- case MOD_TOK: push_val(val1 % val2); break;
- case AND_TOK: push_val(val1 & val2); break;
- case OR_TOK: push_val(val1 | val2); break;
- case XOR_TOK: push_val(val1 ^ val2); break;
- case LSHIFT_TOK: push_val(val1 << val2); break;
- case RSHIFT_TOK: push_val(val1 >> val2); break;
- case EQUAL_TOK: push_val(val1 == val2); break;
- case NE_TOK: push_val(val1 != val2); break;
- case LT_TOK: push_val(val1 < val2); break;
- case GT_TOK: push_val(val1 > val2); break;
- case LE_TOK: push_val(val1 <= val2); break;
- case GE_TOK: push_val(val1 >= val2); break;
-
- case COLON_TOK: op2 = pop_op();
- if (op2 != QUESTION_TOK) {
- goto bad_expr;
- }
- val3 = pop_val();
- push_val(val3 ? val1 : val2);
- break;
- default: goto bad_expr;
- }
- }
-
- /* Enter state S1 or return on end-of-expression. */
- if (token == ZERO) {
- skip_pp();
- val1 = pop_val();
- return val1;
- }
- else {
- psh_op(token);
- goto s1;
- }
-
- bad_expr:
- t_error("bad constant expression--zero assumed");
- skip_pp();
- return 0;
- }
-
-
- /*
- Return TRUE if opr1 has higher precedence than opr2.
- If opr1 and opr2 have the same precedence, return TRUE
- if they associate left to right.
-
- This code reflects the table on page 49 of K & R.
- */
-
- static int
- gt_prec(register int opr1, register int opr2)
- {
- register int prec1, prec2;
-
- TICK("gt_prec");
-
- prec1 = prec(opr1);
- prec2 = prec(opr2);
-
- if (prec1 != prec2) {
- /* Associativity doesn't matter. */
- return prec1 > prec2;
- }
- else if (prec1 == 14 || prec1 == 3 || prec1 == 2) {
- /* Associate right to left. */
- return FALSE;
- }
- else {
- /* Associate left to right. */
- return TRUE;
- }
- }
-
-
- /*
- Return TRUE if c is legal in a file name.
- Assume that the delim character is not legal.
- */
- static bool
- isfnch(int c, int delim)
- {
- TICK("isfnch");
-
- switch (c) {
- case '*':
- case '?':
- case '\n':
- case '\r':
- case ' ':
- case '\t':
- case END_FILE:
- return FALSE;
-
- default: return c != delim;
- }
- }
-
-
- /*
- Return the precedence of an operator.
-
- This code reflects the table on page 49 of K & R.
- */
-
- static int
- prec(int operator)
- {
- TICK("prec");
-
- switch (operator) {
- case TILDE_TOK: return 14;
- case STAR_TOK:
- case DIV_TOK:
- case MOD_TOK: return 13;
- case PLUS_TOK:
- case MINUS_TOK: return 12;
- case LSHIFT_TOK:
- case RSHIFT_TOK: return 11;
- case LT_TOK:
- case LE_TOK:
- case GT_TOK:
- case GE_TOK: return 10;
- case EQUAL_TOK:
- case NE_TOK: return 9;
- case XOR_TOK: return 7;
- case OR_TOK: return 6;
- case COLON_TOK:
- case QUESTION_TOK: return 3;
- case ZERO:
- case ERROR: return ZERO;
- default: return ERROR;
- }
- }
-
-
- /*
- Routines to push and pop the operator and operand stacks.
- */
- static void
- psh_op(int op)
- {
- TICK("psh_op");
-
- if (t_opptr + 1 < t_valptr) {
- t_evalstk [++t_opptr] = op;
- return;
- }
- fatal("psh_op: overflow\n");
- }
-
- static int
- pop_op(void)
- {
- TICK("pop_op");
-
- if (t_opptr >= 0) {
- return t_evalstk [t_opptr--];
- }
- fatal("pop_op: underflow\n");
- }
-
- static void
- push_val(int val)
- {
- TICK("push_val");
- TRACE("push_val", printf("push_val %d\n", val));
-
- if (t_valptr - 1 > t_opptr) {
- t_evalstk [--t_valptr] = val;
- return;
- }
- fatal("push_val: overflow\n");
- }
-
- static int
- pop_val(void)
- {
- TICK("pop_val");
-
- if (t_valptr < MAX_EVAL) {
- TRACE("pop_val", printf("pop_val %d\n", t_evalstk[t_valptr]));
- return t_evalstk [t_valptr++];
- }
- fatal("pop_val: underflow\n");
- }
-
-
- /*
- Handle the #else directive.
- */
- static void
- pp_else(void)
- {
- TICK("pp_else");
-
- if (t_iflevel == 0) {
- t_error("#else ignored--no matching #if.");
- skip_pp();
- }
- else if (t_ifstack [t_iflevel - 1] == TRUE) {
- t_error("Duplicate #else ignored.");
- skip_pp();
- }
- else {
- t_ifstack [t_iflevel - 1] = TRUE;
- skip_lines();
- }
- }
-
-
- /*
- Handle the #endif directive.
- */
- static void
- pp_endif(void)
- {
- TICK("pp_endif");
-
- if (t_iflevel == 0) {
- t_error("#endif ignored--no matching #if.");
- skip_pp();
- }
- else {
- t_iflevel--;
- skip_pp();
- }
- }
-
-
- /*
- Handle the #if directive.
- */
- static void
- pp_if(void)
- {
- TICK("pp_if");
-
- /* Indicate that no #else has been seen. */
- if (t_iflevel >= MAX_IF) {
- t_error("#if ignored--nested too deeply.");
- skip_pp();
- return;
- }
- t_ifstack [t_iflevel++] = FALSE;
-
- /* Skip lines if the constant expression evaluates to zero. */
- if (eval() == 0) {
- /* Skip until #else or #endif. */
- skip_lines();
- }
- }
-
-
- /*
- Handle the #ifdef and #ifndef directives.
-
- flag == TRUE: #ifdef
- flag == FALSE: #ifndef
- */
- static void
- pp_ifdef(int flag)
- {
- register struct mst_node * p;
- struct mst_node * mst_lookup();
-
- TICK("pp_ifdef");
-
- if(t_iflevel > MAX_IF) {
- t_error("#ifdef or #ifndef ignored--nested too deeply.");
- skip_pp();
- return;
- }
-
- if (!isid1(ch)) {
- t_error("#ifdef or #ifndef ignored--identifier expected.");
- skip_pp();
- return;
- }
-
- /* Get id into t_symbol[]. */
- t_id(t_symbol);
- skip_pp();
-
- /* Indicate that no #else has been seen. */
- t_ifstack [t_iflevel++] = FALSE;
-
- /* Skip lines if required. */
- p = mst_lookup(t_symbol);
- if ((flag && p == NULL) || (!flag && p != NULL)) {
- /* Skip until #else or #endif. */
- skip_lines();
- }
- }
-
-
- /*
- Handle the #include directive.
- */
- static void
- pp_incl(void)
- {
- int err_flag;
- char mess [100];
- register int i, j;
- unsigned char f_name [MAX_FILE_NAME];
- unsigned char buffer [MAX_FILE_NAME];
- unsigned char path_name [200];
- unsigned char delim;
-
- TICK("pp_incl");
-
- /* File name is OK. */
- err_flag = FALSE;
-
- /* Check for opening delimiter. */
- if (ch == '"') {
- delim = '"';
- sysnext();
- }
- else if (ch == '<') {
- delim = '>';
- sysnext();
- }
- else {
- err_flag = TRUE;
- delim = '\r';
- }
-
- /* Get file name into f_name[]. */
- for (i = 0; i < MAX_FILE_NAME-1 && isfnch(ch, delim); i++) {
- f_name[i] = ch;
- sysnext();
- }
- f_name[i] = '\0';
-
- if (err_flag || ch != delim) {
- strcpy(mess, "#include ");
- strcat(mess, f_name);
- strcat(mess, " ignored--bad delimiter.");
- t_error(mess);
- skip_pp();
- return;
- }
-
- /* Skip over the delimiter. */
- sysnext();
-
- /*
- Skip the line, including the newline character.
- Do NOT call skip_past(), because we don't want to
- write the old include-level mark here. We also
- don't want to write the NEW include-level mark here
- because we don't know whether sysopen() will fail.
-
- We MUST, however, skip the line here, because after
- sysopen() is called we would not be able to skip
- characters in the old level.
- */
-
- skip_pp();
- if (ch == '\n') {
- sysnext();
- }
- do_nl();
-
- if (t_inlevel >= MAX_INCLUDE) {
- strcpy(mess, "#include ");
- strcat(mess, f_name);
- strcat(mess, " ignored--nested too deeply.");
- t_line--;
- t_error(mess);
- t_line++;
-
- /* Add the include level mark. */
- begin_line(TRUE);
- return;
- }
-
- /* Open the file. */
- if(sysopen(f_name) == FALSE) {
- for (i = 0; i < n_paths; i++) {
- strcpy(path_name, paths [i]);
- strcat(path_name, f_name);
-
- TRACEP("pp_incl", printf("attempting to open %s\n",
- path_name));
-
- if (sysopen(path_name) == TRUE) {
- return;
- }
- }
- strcpy(mess, "#include ");
- strcat(mess, f_name);
- strcat(mess, " ignored--file not found.");
- t_line--;
- t_error(mess);
- t_line++;
- }
-
- begin_line(TRUE);
- }
-
-
- /*
- Set the line number and file name.
- */
- static void
- pp_line(void)
- {
- register int i;
-
- TICK("pp_line");
-
- (void) t_number();
- t_line = CAST(int) t_value;
-
- /* See if an optional file name is present. */
- skip_bl();
- if (ch == '\n' || ch == '\r' || ch == END_FILE) {
- return;
- }
-
- /* Copy line to t_file[]. */
- for(i = 0; i < MAX_FILE_NAME - 1; i++) {
- if (ch == END_FILE || ch == '\n' || ch == '\r') {
- break;
- }
- t_file[i] = CAST(char) ch;
- sysnext();
- }
- t_file [i] = '\0';
- skip_pp();
- }
-
-
- /*
- Undefine a previously #defined variable.
- */
- static void
- pp_undef(void)
- {
- TICK("pp_undef");
-
- /* Get the identifier into t_symbol[]. */
- if (!isid1(ch)) {
- t_error("#undef ignored--identifier expected.");
- skip_pp();
- return;
- }
- t_id(t_symbol);
-
- /* Delete the identifier. Give no warning if it doesn't exist. */
- mst_delete(t_symbol);
- skip_pp();
- }
-
-
- /*
- Skip lines until a matching #else or #endif directive is found.
- Thus, interior #ifdef, #ifndef, #if and #else directives must
- be recognized and dealt with.
-
- A fine point: This code skips lines without doing any parsing of the
- line. The ONLY things this code looks for are lines which start with
- #ifdef, #ifndef, #if or #else. One should not detect "unknown"
- preprocessor directives or other possible "errors," for the very good
- reason that one wants to use conditional compilation to exclude lines
- which might not even be written in the C language.
-
- Examples can be given where this action might be suspect. Consider a
- string which is continued from one line to the next using the backslash
- newline convention. If the continuation line starts with one of the
- four directives that this routine is looking for then either the lex or
- the person using the lex will become confused. This is a minor hole in
- the C language, and it seems better to ignore the fine points and do
- what is sensible in most cases, namely skip entire lines as a unit.
-
- The routine skip_past() is used to do the skipping of exactly 1 line.
- */
-
- #define EQL(string) str_eq(t_symbol,string)
- static void
- skip_lines(void)
- {
- register int level;
-
- TICK("skip_lines");
-
- /* Just in case. */
- if (t_iflevel <= 0) {
- fatal("skip_lines: can't happen.");
- }
-
- /* No inner nexting yet. */
- level = 0;
-
- /* Start a new line. */
- loop:
- TICK("skip_lines1");
-
- if (ch == END_FILE) {
- t_error("File ends inside range of #ifdef.");
- return;
- }
-
- if (ch == '\r') {
- sysnext();
- goto loop;
- }
-
- /* Skip the line if it doesn't start with '#'. */
- if (ch != '#') {
- skip_past();
- goto loop;
- }
-
- /* Skip the line if '#' isn't followed by an id. */
- sysnext();
- if (!isid1(ch)) {
- skip_past();
- goto loop;
- }
-
- /* Get the directive into t_symbol[]. */
- t_id(t_symbol);
-
- if (EQL("ifdef") || EQL("ifndef") || EQL("if")) {
- level++;
- }
- else if (EQL("else")) {
- if (level > 0) {
- ;
- }
- else if (t_ifstack [t_iflevel - 1] == FALSE) {
- t_ifstack [t_iflevel - 1] = TRUE;
- skip_pp();
- return;
- }
- else {
- t_error("Extra #else ignored.");
- }
- }
- else if (EQL("endif")) {
- if (level > 0) {
- level--;
- }
- else {
- t_iflevel--;
- skip_pp();
- return;
- }
- }
- else {
- skip_past();
- }
- goto loop;
- }
-
- #undef EQL
-