bin/60914: m4 C operators are not

KS Braunsdorf m4 at ksb.npcguild.org
Sun Jan 4 21:20:18 PST 2004


>Number:         60914
>Category:       bin
>Synopsis:       m4 C operators are not
>Confidential:   no
>Severity:       critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sun Jan 04 21:20:10 PST 2004
>Closed-Date:
>Last-Modified:
>Originator:     KS Braunsdorf
>Release:        FreeBSD 4.9-STABLE i386
>Organization:
NonPlayer Character Guild
>Environment:
System: FreeBSD proxy.npcguild.org 4.9-STABLE FreeBSD 4.9-STABLE #0: Sun Jan 4 15:30:02 CST 2004 ksb at proxy.npcguild.org:/usr/obj/usr/src/sys/GENERIC i386


	All version of m4 under FreeBSD I can test 2.1 to 5.1
>Description:
	m4 doesn't do what the manaul page says it should for many
	of the C operators.  It do not work like m4 on other UNIX-like
	operating system either.
>How-To-Repeat:

These operators are just plain wrong (not the bitwise ones):
	$ echo "eval(7&3)" | m4
	should ouptut "3", not "1".
	$ echo "eval(5^3)" | m4
	should output "6", not "125"
	$ echo "eval(5|3)" | m4
	should output "7", not "1"

Since a C programmer depends on && and || being short circut (I think)
	$ echo "eval(0&&4/0)" | m4
	should ouptut "0", not "m4: division by zero in eval."
	$ echo "eval(1||7/0)" | m4
	should ouyput "1", not "m4: division by zero in eval."

I think the error message for some cases we unclear, for example:
	$ echo "eval(4?78)" | m4
	m4: bad query, missing ":" in expr 4?78.
	is better than
	$ echo "eval(4?78)" | /usr/bin/OLD/m4
	m4: bad query in expr 4?78.

The precedence of both == and != were wrong as was the precedence of
"!".  I fixed thise too.

I left in the ** (eponent) operator, as it doesn't conflict with the
C set, but it is not documented, either.

>Fix:
	The patch below fixes these issues.


--- expr.c.orig	Sun Jan  4 18:08:50 2004
+++ expr.c	Sun Jan  4 22:44:47 2004
@@ -60,45 +60,41 @@
 
 /*
- *      expression evaluator: performs a standard recursive
- *      descent parse to evaluate any expression permissible
- *      within the following grammar:
+ * Expression evaluator: performs a standard recursive descent
+ * parse to evaluate any expression permissible within the 
+ * following grammar, which should be C's operator set.
+ * N.B. We've added exponents (unary ** expr) for no good reason.
  *
- *      expr    :       query EOS
- *      query   :       lor
- *              |       lor "?" query ":" query
- *      lor     :       land { "||" land }
- *      land    :       not { "&&" not }
- *	not	:	eqrel
- *		|	'!' not
- *      eqrel   :       shift { eqrelop shift }
- *      shift   :       primary { shop primary }
- *      primary :       term { addop term }
- *      term    :       exp { mulop exp }
- *	exp	:	unary { expop unary }
- *      unary   :       factor
- *              |       unop unary
- *      factor  :       constant
- *              |       "(" query ")"
- *      constant:       num
- *              |       "'" CHAR "'"
- *      num     :       DIGIT
- *              |       DIGIT num
- *      shop    :       "<<"
- *              |       ">>"
- *      eqrel   :       "="
- *              |       "=="
- *              |       "!="
- *      	|       "<"
- *              |       ">"
- *              |       "<="
- *              |       ">="
+ *	expr	:	query EOS
+ *	query	:	lor
+ *		|	lor "?" query ":" query
+ *	lor	:	land { "||" land } *
+ *	land	:	nor { "&&" bor } *
+ *	bor	:	xor { "|" xor } *
+ *	xor	:	band { "^" band } *
+ *	band	:	eqrel { "&" eqrel } *
+ *	eqrel	:	nerel { ("==" | "!=") nerel } *
+ *	nerel	:	shift { ("<" | ">" | "<=" | ">=") shift } *
+ *	shift	:	primary { ("<<" | ">>") primary } *
+ *	primary :	term { ("+" | "-") term } *
+ *	term	:	exp { ("*" | "/" | "%") exp } *
+ *	exp	:	unary { "**" exp } *			! not doc'd
+ *	unary	:	factor
+ *		|	("+" | "-" | "~" | "!") unary
+ *	factor	:	constant
+ *		|	"(" query ")"
+ *	constant:	num
+ *		|	"'" CHAR "'"
+ *	num	:	DIGIT
+ *		|	DIGIT num
+ *			0xHEX ?
  *
  *
- *      This expression evaluator is lifted from a public-domain
- *      C Pre-Processor included with the DECUS C Compiler distribution.
- *      It is hacked somewhat to be suitable for m4.
+ *	This expression evaluator is lifted from a public-domain
+ *	C Pre-Processor included with the DECUS C Compiler distribution.
+ *	It is hacked somewhat to be suitable for m4.
  *
- *      Originally by:  Mike Lutz
- *                      Bob Harper
+ *	Originally by:	Mike Lutz
+ *			Bob Harper
+ *	Made closer to the C operator set by KSBraunsdorf
  */
 
@@ -113,5 +109,5 @@
 #define HEX	16
 
-static const char *nxtch;		       /* Parser scan pointer */
+static const char *nxtch;		/* Parser scan pointer */
 static const char *where;
 
@@ -119,6 +115,9 @@
 static int lor(void);
 static int land(void);
-static int not(void);
+static int bor(void);
+static int xor(void);
+static int band(void);
 static int eqrel(void);
+static int nerel(void);
 static int shift(void);
 static int primary(void);
@@ -129,5 +128,4 @@
 static int constant(void);
 static int num(void);
-static int geteqrel(void);
 static int skipws(void);
 static void experr(const char *);
@@ -138,4 +136,8 @@
 #include <setjmp.h>
 static jmp_buf expjump;
+/* for short circut expression
+ * for exmple "0 && 10/0" should return 0, not an exception -- ksb
+ */
+static int mayeval = 0;
 
 /*
@@ -144,6 +146,6 @@
  *      getch   - return the next character from expr string.
  */
-#define ungetch()       nxtch--
-#define getch()         *nxtch++
+#define ungetch()	nxtch--
+#define getch()		*nxtch++
 
 int
@@ -154,4 +156,5 @@
 	nxtch = expbuf;
 	where = expbuf;
+	mayeval = 1;
 	if (setjmp(expjump) != 0)
 		return FALSE;
@@ -171,6 +174,7 @@
 query(void)
 {
-	int result, true_val, false_val;
+	int result, true_val, false_val, waseval;
 
+	waseval = mayeval;
 	result = lor();
 	if (skipws() != '?') {
@@ -179,24 +183,34 @@
 	}
 
+	mayeval = result;
 	true_val = query();
 	if (skipws() != ':')
-		experr("bad query");
+		experr("bad query, missing \":\"");
 
+	mayeval = !result;
 	false_val = query();
+	mayeval = waseval;
 	return result ? true_val : false_val;
 }
 
 /*
- * lor : land { '||' land }
+ * lor : land { '||' land }, but we don't short out divide by zero
+ * or the like.  We should.
  */
 static int
 lor(void)
 {
-	int c, vl, vr;
+	int c, vl, vr, waseval;
 
+	waseval = mayeval;
 	vl = land();
 	while ((c = skipws()) == '|') {
-		if (getch() != '|')
+		if (getch() != '|') {
 			ungetch();
+			break;
+		}
+		if (0 != vl) {
+			mayeval = 0;
+		}
 		vr = land();
 		vl = vl || vr;
@@ -204,4 +218,5 @@
 
 	ungetch();
+	mayeval = waseval;
 	return vl;
 }
@@ -213,78 +228,144 @@
 land(void)
 {
-	int c, vl, vr;
+	int c, vl, vr, waseval;
 
-	vl = not();
+	waseval = mayeval;
+	vl = bor();
 	while ((c = skipws()) == '&') {
-		if (getch() != '&')
+		if (getch() != '&') {
 			ungetch();
-		vr = not();
+			break;
+		}
+		if (0 == vl) {
+			mayeval = 0;
+		}
+		vr = bor();
 		vl = vl && vr;
 	}
 
 	ungetch();
+	mayeval = waseval;
 	return vl;
 }
 
 /*
- * not : eqrel | '!' not
+ * or : xor { '|' xor } *
  */
 static int
-not(void)
+bor(void)
 {
-	int val, c;
+	int vl, vr, c, cr;
 
-	if ((c = skipws()) == '!' && getch() != '=') {
+	vl = xor();
+	while ((c = skipws()) == '|') {
+		cr = getch();
 		ungetch();
-		val = not();
-		return !val;
+		if ('|' == cr) {
+			break;
+		}
+		vr = xor();
+		vl |= vr;
 	}
+	ungetch();
+	return vl;
+}
 
-	if (c == '!')
-		ungetch();
+/*
+ * xor : and { '|' and } *
+ */
+static int
+xor(void)
+{
+	int vl, vr, c;
+
+	vl = band();
+	while ((c = skipws()) == '^') {
+		vr = band();
+		vl ^= vr;
+	}
 	ungetch();
-	return eqrel();
+	return vl;
 }
 
 /*
- * eqrel : shift { eqrelop shift }
+ * band : eqrel { '&' eqrel } *
  */
 static int
-eqrel(void)
+band(void)
 {
-	int vl, vr, op;
+	int vl, vr, c, cr;
 
-	vl = shift();
-	while ((op = geteqrel()) != -1) {
-		vr = shift();
+	vl = eqrel();
+	while ((c = skipws()) == '&') {
+		cr = getch();
+		ungetch();
+		if ('&' == cr) {
+			break;
+		}
+		vr = eqrel();
+		vl &= vr;
+	}
+	ungetch();
+	return vl;
+}
 
-		switch (op) {
+/*
+ * eqrel : nerel { ("==" | "!=") nerel } *
+ */
+static int
+eqrel(void)
+{
+	int vl, vr, c, c2;
 
-		case EQL:
+	vl = nerel();
+	while ((c = skipws()) == '!' || c == '=') {
+		if ((c2 = getch()) != '=') {
+			ungetch();
+			break;
+		}
+		vr = nerel();
+		switch (c) {
+		case '=':
 			vl = (vl == vr);
 			break;
-		case NEQ:
+		case '!':
 			vl = (vl != vr);
-			break;
+		break;
+		}
+	}
+	ungetch();
+	return vl;
+}
 
-		case LEQ:
-			vl = (vl <= vr);
-			break;
-		case LSS:
-			vl = (vl < vr);
-			break;
-		case GTR:
-			vl = (vl > vr);
+/*
+ * nerel : shift { ("<=" | ">=" | "<" | ">") shift } *
+ */
+static int
+nerel(void)
+{
+	int vl, vr, c, c2;
+
+	vl = shift();
+	while ((c = skipws()) == '<' || c == '>') {
+		if ((c2 = getch()) != '=') {
+			ungetch();
+			c2 = ' ';
+		}
+		vr = shift();
+		switch (c) {
+		case '<':
+			vl = ' ' == c2 ? (vl < vr) : (vl <= vr);
 			break;
-		case GEQ:
-			vl = (vl >= vr);
+		case '>':
+			vl = ' ' == c2 ? (vl > vr) : (vl >= vr);
 			break;
 		}
 	}
+	ungetch();
 	return vl;
 }
 
 /*
- * shift : primary { shop primary }
+ * shift : primary { ("<<" | ">>") primary }
  */
 static int
@@ -294,5 +375,9 @@
 
 	vl = primary();
-	while (((c = skipws()) == '<' || c == '>') && getch() == c) {
+	while (((c = skipws()) == '<' || c == '>')) {
+		if (getch() != c) {
+			ungetch();
+			break;
+		}
 		vr = primary();
 
@@ -302,7 +387,4 @@
 			vl >>= vr;
 	}
-
-	if (c == '<' || c == '>')
-		ungetch();
 	ungetch();
 	return vl;
@@ -310,5 +392,5 @@
 
 /*
- * primary : term { addop term }
+ * primary : term { ("+" | "-") term } *
  */
 static int
@@ -332,5 +414,5 @@
 
 /*
- * <term> := <exp> { <mulop> <exp> }
+ * term := exp { ("*" | "/" | "%") exp } *
  */
 static int
@@ -348,5 +430,7 @@
 			break;
 		case '/':
-			if (vr == 0)
+			if (! mayeval)
+				/* nada */;
+			else if (vr == 0)
 				errx(1, "division by zero in eval.");
 			else
@@ -354,5 +438,7 @@
 			break;
 		case '%':
-			if (vr == 0)
+			if (! mayeval)
+				/* nada */;
+			else if (vr == 0)
 				errx(1, "modulo zero in eval.");
 			else
@@ -366,5 +452,5 @@
 
 /*
- * <term> := <unary> { <expop> <unary> }
+ * term := unary { "**" exp } *
  */
 static int
@@ -374,13 +460,9 @@
 
 	vl = unary();
-	switch (c = skipws()) {
-
-	case '*':
+	while ((c = skipws()) == '*') {
 		if (getch() != '*') {
 			ungetch();
 			break;
 		}
-
-	case '^':
 		vr = exp();
 		n = 1;
@@ -389,5 +471,4 @@
 		return n;
 	}
-
 	ungetch();
 	return vl;
@@ -395,5 +476,5 @@
 
 /*
- * unary : factor | unop unary
+ * unary : factor | ("+" | "-" | "~" | "!")  unary
  */
 static int
@@ -402,5 +483,5 @@
 	int val, c;
 
-	if ((c = skipws()) == '+' || c == '-' || c == '~') {
+	if ((c = skipws()) == '+' || c == '-' || c == '~' || c == '!') {
 		val = unary();
 
@@ -412,4 +493,6 @@
 		case '~':
 			return ~val;
+		case '!':
+			return !val;
 		}
 	}
@@ -430,5 +513,5 @@
 		val = query();
 		if (skipws() != ')')
-			experr("bad factor");
+			experr("missing close parenthesis");
 		return val;
 	}
@@ -526,8 +609,8 @@
 		switch(c) {
 			case '8': case '9':
-				if (base == OCTAL) 
+				if (base == OCTAL)
 					goto bad_digit;
 				/*FALLTHRU*/
-			case '0': case '1': case '2': case '3': 
+			case '0': case '1': case '2': case '3':
 			case '4': case '5': case '6': case '7':
 				rval *= base;
@@ -551,53 +634,9 @@
 bad_digit:
 	ungetch();
-	
-	if (ndig == 0)
-		experr("bad constant");
-	
-	return rval;
-}
-
-/*
- * eqrel : '=' | '==' | '!=' | '<' | '>' | '<=' | '>='
- */
-static int
-geteqrel(void)
-{
-	int c1, c2;
-
-	c1 = skipws();
-	c2 = getch();
-
-	switch (c1) {
-
-	case '=':
-		if (c2 != '=')
-			ungetch();
-		return EQL;
 
-	case '!':
-		if (c2 == '=')
-			return NEQ;
-		ungetch();
-		ungetch();
-		return -1;
-
-	case '<':
-		if (c2 == '=')
-			return LEQ;
-		ungetch();
-		return LSS;
-
-	case '>':
-		if (c2 == '=')
-			return GEQ;
-		ungetch();
-		return GTR;
+	if (ndig == 0)
+		experr("no digits in constant");
 
-	default:
-		ungetch();
-		ungetch();
-		return -1;
-	}
+	return rval;
 }
 
@@ -616,5 +655,5 @@
 
 /*
- * resets environment to eval(), prints an error 
+ * resets environment to eval(), prints an error
  * and forces eval to return FALSE.
  */
>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list