NetBSD/usr.bin/m4/expr.c

618 lines
14 KiB
C

/* File : expr.c
Authors: Mike Lutz & Bob Harper
Editors: Ozan Yigit & Richard A. O'Keefe
Updated: %G%
Purpose: arithmetic expression evaluator.
expr() performs a standard recursive descent parse to evaluate any
expression permitted byf the following grammar:
expr : query EOS
query : lor
| lor "?" query ":" query
lor : land { "||" land } or OR, for Pascal
land : bor { "&&" bor } or AND, for Pascal
bor : bxor { "|" bxor }
bxor : band { "^" band }
band : eql { "&" eql }
eql : relat { eqrel relat }
relat : shift { rel shift }
shift : primary { shop primary }
primary : term { addop term }
term : unary { mulop unary }
unary : factor
| unop unary
factor : constant
| "(" query ")"
constant: num
| "'" CHAR "'" or '"' CHAR '"'
num : DIGIT full ANSI C syntax
| DIGIT num
shop : "<<"
| ">>"
eqlrel : "="
| "=="
| "!="
rel : "<" or <>, Pascal not-equal
| ">"
| "<=" or =<, for Prolog users.
| ">="
This expression evaluator was lifted from a public-domain
C Pre-Processor included with the DECUS C Compiler distribution.
It has been hacked somewhat to be suitable for m4.
26-Mar-1993 Changed to work in any of EBCDIC, ASCII, DEC MNCS,
or ISO 8859/n.
26-Mar-1993 Changed to use "long int" rather than int, so that
we get the same 32-bit arithmetic on a PC as on a Sun.
It isn't fully portable, of course, but then on a 64-
bit machine we _want_ 64-bit arithmetic...
Shifting rewritten (using LONG_BIT) to give signed
shifts even when (long) >> (long) is unsigned.
26-Mar-1993 I finally got sick of the fact that &&, ||, and ?:
don't do conditional evaluation. What is the good
of having eval(0&&(1/0)) crash and dump core? Now
every function has a doit? argument.
26-Mar-1993 charcon() didn't actually accept 'abcd', which it
should have. Fixed it.
20-Apr-1993 eval(1/0) and eval(1%0) dumped core and crashed.
This is also true of the System V r 3.2 m4, but
it isn't good enough for ours! Changed it so that
x % 0 => x as per Concrete Mathematics
x / 0 => error and return 0 from expr().
*/
#define FALSE 0
#define TRUE 1
#include <stdio.h>
#include <setjmp.h>
static jmp_buf expjump; /* Error exit point for expr() */
static unsigned char *nxtchr; /* Parser scan pointer */
#define deblank0 while ((unsigned)(*nxtchr-1) < ' ') nxtchr++
#define deblank1 while ((unsigned)(*++nxtchr - 1) < ' ')
#define deblank2 nxtchr++; deblank1
#include "ourlims.h"
static char digval[1+UCHAR_MAX];
/* This file should work in any C implementation that doesn't have too
many characters to fit in one table. We use a table to convert
(unsigned) characters to numeric codes:
0 to 9 for '0' to '9'
10 to 35 for 'a' to 'z'
10 to 35 for 'A' to 'Z'
36 for '_'
Instead of asking whether tolower(c) == 'a' we ask whether
digval[c] == DIGIT_A, and so on. This essentially duplicates the
chtype[] table in main.c; we should use just one table.
*/
#define DIGIT_A 10
#define DIGIT_B 11
#define DIGIT_C 12
#define DIGIT_D 13
#define DIGIT_E 14
#define DIGIT_F 15
#define DIGIT_G 16
#define DIGIT_H 17
#define DIGIT_I 18
#define DIGIT_J 19
#define DIGIT_K 20
#define DIGIT_L 21
#define DIGIT_M 22
#define DIGIT_N 23
#define DIGIT_O 24
#define DIGIT_P 25
#define DIGIT_Q 26
#define DIGIT_R 27
#define DIGIT_S 28
#define DIGIT_T 29
#define DIGIT_U 30
#define DIGIT_V 31
#define DIGIT_W 32
#define DIGIT_X 33
#define DIGIT_Y 34
#define DIGIT_Z 35
#ifdef __STDC__
static long int query(int);
#else
static long int query();
#endif
/* experr(msg)
prints an error message, resets environment to expr(), and
forces expr() to return FALSE.
*/
void experr(msg)
char *msg;
{
(void) fprintf(stderr, "m4: %s\n", msg);
longjmp(expjump, -1); /* Force expr() to return FALSE */
}
/* <numcon> ::= '0x' <hex> | '0X' <hex> | '0' <oct> | <dec>
For ANSI C, an integer may be followed by u, l, ul, or lu,
in any mix of cases. We accept and ignore those letters;
all the numbers are treated as long.
*/
static long int numcon(doit)
int doit;
{
register long int v; /* current value */
register int b; /* base (radix) */
register int c; /* character or digit value */
if (!doit) {
do nxtchr++; while (digval[*nxtchr] <= 36);
deblank0;
return 0;
}
v = digval[*nxtchr++]; /* We already know it's a digit */
if (v != 0) {
b = 10; /* decimal number */
} else
if (digval[*nxtchr] == DIGIT_X) {
nxtchr++;
b = 16; /* hexadecimal number */
} else {
b = 8; /* octal number */
}
do {
while (digval[c = *nxtchr++] < b) v = v*b + digval[c];
} while (c == '_');
while (digval[c] == DIGIT_L || digval[c] == DIGIT_U) c = *nxtchr++;
nxtchr--; /* unread c */
if ((unsigned)(c-1) < ' ') { deblank1; }
return v;
}
/* <charcon> ::= <qt> { <char> } <qt>
Note: multibyte constants are accepted.
Note: BEL (\a) and ESC (\e) have the same values in EBCDIC and ASCII.
*/
static long int charcon(doit)
int doit;
{
register int i;
long int value;
register int c;
int q;
int v[sizeof value];
q = *nxtchr++; /* the quote character */
for (i = 0; ; i++) {
c = *nxtchr++;
if (c == q) { /* end of literal, or doubled quote */
if (*nxtchr != c) break;
nxtchr++; /* doubled quote stands for one quote */
}
if (i == sizeof value) experr("Unterminated character constant");
if (c == '\\') {
switch (c = *nxtchr++) {
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c -= '0';
if ((unsigned)(*nxtchr - '0') < 8)
c = (c << 3) | (*nxtchr++ - '0');
if ((unsigned)(*nxtchr - '0') < 8)
c = (c << 3) | (*nxtchr++ - '0');
break;
case 'n': case 'N': c = '\n'; break;
case 'r': case 'R': c = '\r'; break;
case 't': case 'T': c = '\t'; break;
case 'b': case 'B': c = '\b'; break;
case 'f': case 'F': c = '\f'; break;
case 'a': case 'A': c = 007; break;
case 'e': case 'E': c = 033; break;
#if ' ' == 64
case 'd': case 'D': c = 045; break; /*EBCDIC DEL */
#else
case 'd': case 'D': c = 127; break; /* ASCII DEL */
#endif
default : break;
}
}
v[i] = c;
}
deblank0;
if (!doit) return 0;
for (value = 0; --i >= 0; ) value = (value << CHAR_BIT) | v[i];
return value;
}
/* <unary> ::= <unop> <unary> | <factor>
<unop> ::= '!' || '~' | '-'
<factor> ::= '(' <query> ')' | <'> <char> <'> | <"> <char> <"> | <num>
*/
static long int unary(doit)
int doit;
{
long int v;
switch (nxtchr[0]) {
case 'n': case 'N':
if (digval[nxtchr[1]] != DIGIT_O
|| digval[nxtchr[2]] != DIGIT_T)
experr("Bad 'not'");
nxtchr += 2;
case '!': deblank1; return !unary(doit);
case '~': deblank1; return ~unary(doit);
case '-': deblank1; return -unary(doit);
case '+': deblank1; return unary(doit);
case '(': deblank1; v = query(doit);
if (nxtchr[0] != ')') experr("Bad factor");
deblank1; return v;
case '\'':
case '\"': return charcon(doit);
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
case '9': return numcon(doit);
default : experr("Bad constant");
}
return 0; /*NOTREACHED*/
}
/* <term> ::= <unary> { <mulop> <unary> }
<mulop> ::= '*' | '/' || '%'
*/
static long int term(doit)
int doit;
{
register long int vl, vr;
vl = unary(doit);
for (;;)
switch (nxtchr[0]) {
case '*':
deblank1;
vr = unary(doit);
if (doit) vl *= vr;
break;
case 'd': case 'D':
if (digval[nxtchr[1]] != DIGIT_I
|| digval[nxtchr[2]] != DIGIT_V)
experr("Bad 'div'");
nxtchr += 2;
/*FALLTHROUGH*/
case '/':
deblank1;
vr = unary(doit);
if (doit) {
if (vr == 0) experr("Division by 0");
vl /= vr;
}
break;
case 'm': case 'M':
if (digval[nxtchr[1]] != DIGIT_O
|| digval[nxtchr[2]] != DIGIT_D)
experr("Bad 'mod'");
nxtchr += 2;
/*FALLTHROUGH*/
case '%':
deblank1;
vr = unary(doit);
if (doit) {
if (vr != 0) vl %= vr;
}
break;
default:
return vl;
}
/*NOTREACHED*/
}
/* <primary> ::= <term> { <addop> <term> }
<addop> ::= '+' | '-'
*/
static long int primary(doit)
int doit;
{
register long int vl;
vl = term(doit);
for (;;)
if (nxtchr[0] == '+') {
deblank1;
if (doit) vl += term(doit); else (void)term(doit);
} else
if (nxtchr[0] == '-') {
deblank1;
if (doit) vl -= term(doit); else (void)term(doit);
} else
return vl;
/*NOTREACHED*/
}
/* <shift> ::= <primary> { <shop> <primary> }
<shop> ::= '<<' | '>>'
*/
static long int shift(doit)
int doit;
{
register long int vl, vr;
vl = primary(doit);
for (;;) {
if (nxtchr[0] == '<' && nxtchr[1] == '<') {
deblank2;
vr = primary(doit);
} else
if (nxtchr[0] == '>' && nxtchr[1] == '>') {
deblank2;
vr = -primary(doit);
} else {
return vl;
}
/* The following code implements shifts portably */
/* Shifts are signed shifts, and the shift count */
/* acts like repeated one-bit shifts, not modulo anything */
if (doit) {
if (vr >= LONG_BIT) {
vl = 0;
} else
if (vr <= -LONG_BIT) {
vl = -(vl < 0);
} else
if (vr > 0) {
vl <<= vr;
} else
if (vr < 0) {
vl = (vl >> -vr) | (-(vl < 0) << (LONG_BIT + vr));
}
}
}
/*NOTREACHED*/
}
/* <relat> ::= <shift> { <rel> <shift> }
<rel> ::= '<=' | '>=' | '=<' | '=>' | '<' | '>'
Here I rely on the fact that '<<' and '>>' are swallowed by <shift>
*/
static long int relat(doit)
int doit;
{
register long int vl;
vl = shift(doit);
for (;;)
switch (nxtchr[0]) {
case '=':
switch (nxtchr[1]) {
case '<': /* =<, take as <= */
deblank2;
vl = vl <= shift(doit);
break;
case '>': /* =>, take as >= */
deblank2;
vl = vl >= shift(doit);
break;
default: /* == or =; OOPS */
return vl;
}
break;
case '<':
if (nxtchr[1] == '=') { /* <= */
deblank2;
vl = vl <= shift(doit);
} else
if (nxtchr[1] == '>') { /* <> (Pascal) */
deblank2;
vl = vl != shift(doit);
} else { /* < */
deblank1;
vl = vl < shift(doit);
}
break;
case '>':
if (nxtchr[1] == '=') { /* >= */
deblank2;
vl = vl >= shift(doit);
} else { /* > */
deblank1;
vl = vl > shift(doit);
}
break;
default:
return vl;
}
/*NOTREACHED*/
}
/* <eql> ::= <relat> { <eqrel> <relat> }
<eqlrel> ::= '!=' | '==' | '='
*/
static long int eql(doit)
int doit;
{
register long int vl;
vl = relat(doit);
for (;;)
if (nxtchr[0] == '!' && nxtchr[1] == '=') {
deblank2;
vl = vl != relat(doit);
} else
if (nxtchr[0] == '=' && nxtchr[1] == '=') {
deblank2;
vl = vl == relat(doit);
} else
if (nxtchr[0] == '=') {
deblank1;
vl = vl == relat(doit);
} else
return vl;
/*NOTREACHED*/
}
/* <band> ::= <eql> { '&' <eql> }
*/
static long int band(doit)
int doit;
{
register long int vl;
vl = eql(doit);
while (nxtchr[0] == '&' && nxtchr[1] != '&') {
deblank1;
if (doit) vl &= eql(doit); else (void)eql(doit);
}
return vl;
}
/* <bxor> ::= <band> { '^' <band> }
*/
static long int bxor(doit)
int doit;
{
register long int vl;
vl = band(doit);
while (nxtchr[0] == '^') {
deblank1;
if (doit) vl ^= band(doit); else (void)band(doit);
}
return vl;
}
/* <bor> ::= <bxor> { '|' <bxor> }
*/
static long int bor(doit)
int doit;
{
register long int vl;
vl = bxor(doit);
while (nxtchr[0] == '|' && nxtchr[1] != '|') {
deblank1;
if (doit) vl |= bxor(doit); else (void)bxor(doit);
}
return vl;
}
/* <land> ::= <bor> { '&&' <bor> }
*/
static long int land(doit)
int doit;
{
register long int vl;
vl = bor(doit);
for (;;) {
if (nxtchr[0] == '&') {
if (nxtchr[1] != '&') break;
deblank2;
} else
if (digval[nxtchr[0]] == DIGIT_A) {
if (digval[nxtchr[1]] != DIGIT_N) break;
if (digval[nxtchr[2]] != DIGIT_D) break;
nxtchr += 2; deblank1;
} else {
/* neither && nor and */
break;
}
vl = bor(doit && vl) != 0;
}
return vl;
}
/* <lor> ::= <land> { '||' <land> }
*/
static long int lor(doit)
int doit;
{
register long int vl;
vl = land(doit);
for (;;) {
if (nxtchr[0] == '|') {
if (nxtchr[1] != '|') break;
} else
if (digval[nxtchr[0]] == DIGIT_O) {
if (digval[nxtchr[1]] != DIGIT_R) break;
} else {
/* neither || nor or */
break;
}
deblank2;
vl = land(doit && !vl) != 0;
}
return vl;
}
/* <query> ::= <lor> [ '?' <query> ':' <query> ]
*/
static long int query(doit)
int doit;
{
register long int bool, true_val, false_val;
bool = lor(doit);
if (*nxtchr != '?') return bool;
deblank1;
true_val = query(doit && bool);
if (*nxtchr != ':') experr("Bad query");
deblank1;
false_val = query(doit && !bool);
return bool ? true_val : false_val;
}
static void initialise_digval()
{
register unsigned char *s;
register int c;
for (c = 0; c <= UCHAR_MAX; c++) digval[c] = 99;
for (c = 0, s = (unsigned char *)"0123456789";
/*while*/ *s;
/*doing*/ digval[*s++] = c++) /* skip */;
for (c = 10, s = (unsigned char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/*while*/ *s;
/*doing*/ digval[*s++] = c++) /* skip */;
for (c = 10, s = (unsigned char *)"abcdefghijklmnopqrstuvwxyz";
/*while*/ *s;
/*doing*/ digval[*s++] = c++) /* skip */;
digval['_'] = 36;
}
long int expr(expbuf)
char *expbuf;
{
register int rval;
if (digval['1'] == 0) initialise_digval();
nxtchr = (unsigned char *)expbuf;
deblank0;
if (setjmp(expjump) != 0) return FALSE;
rval = query(TRUE);
if (*nxtchr) experr("Ill-formed expression");
return rval;
}