m4-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

improve eval


From: Eric Blake
Subject: improve eval
Date: Wed, 24 Dec 2008 21:49:20 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

I noticed a FIXME in master's modules/evalparse.c which can be addressed now 
that POSIX 2008 is released.  Basically, POSIX no longer forbids the use of 
extension operators, and therefore it is sufficient to warn (for consistency) 
rather than fail on an operator that we recognize but don't support.  While I 
was modifying the message strings, I also added the ?: operator to branch-1.6.  
I'm applying these two patches (the first to branch-1.6, the second to master):

From: Eric Blake <address@hidden>
Date: Wed, 24 Dec 2008 14:15:13 -0700
Subject: [PATCH] Enhance eval, as allowed by POSIX 2008.

* src/eval.c (enum eval_token): Add QUESTION and COLON.
(enum eval_error): Add MISSING_COLON.
(condition_term): New function.
(eval_lex, simple_term): Support new operator.
(evaluate): Likewise.  Warn, not error, on invalid operator.
* doc/m4.texinfo (Eval): Update documentation.
(Improved forloop): Adjust test.
* NEWS: Document the change.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog      |   12 +++++++++
 NEWS           |    4 +++
 doc/m4.texinfo |   30 ++++++++++++++--------
 src/eval.c     |   75 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
 4 files changed, 103 insertions(+), 18 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7f3bde2..d15e52a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2008-12-24  Eric Blake  <address@hidden>
+
+       Enhance eval, as allowed by POSIX 2008.
+       * src/eval.c (enum eval_token): Add QUESTION and COLON.
+       (enum eval_error): Add MISSING_COLON.
+       (condition_term): New function.
+       (eval_lex, simple_term): Support new operator.
+       (evaluate): Likewise.  Warn, not error, on invalid operator.
+       * doc/m4.texinfo (Eval): Update documentation.
+       (Improved forloop): Adjust test.
+       * NEWS: Document the change.
+
 2008-12-23  Eric Blake  <address@hidden>
 
        Issue deprecation warning for -o/--error-output.
diff --git a/NEWS b/NEWS
index ebd2cbb..2e1a286 100644
--- a/NEWS
+++ b/NEWS
@@ -107,6 +107,10 @@ Foundation, Inc.
    context of a macro name, rather than acting on the empty string.  This
    was already done for `define', `pushdef', `builtin', and `indir'.
 
+** Enhance the `eval' builtin to understand the `?:' operator, and
+   downgrade a failed parse due to an unknown operator from an error to a
+   warning.
+
 ** A number of portability improvements inherited from gnulib.
 
 * Noteworthy changes in Version 1.4.10b (2008-02-25) [beta]
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 2ab7290..93adb64 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -6670,6 +6670,8 @@ Eval
 Logical and
 @item ||
 Logical or
address@hidden ?:
+Conditional ternary
 @end table
 
 The macro @code{eval} is recognized only with parameters.
@@ -6678,7 +6680,7 @@ Eval
 All binary operators, except exponentiation, are left associative.  C
 operators that perform variable assignment, such as @samp{+=} or
 @samp{--}, are not implemented, since @code{eval} only operates on
-constants, not variables.  Attempting to use them results in an error.
+constants, not variables.  Attempting to use them results in a warning.
 However, since traditional implementations treated @samp{=} as an
 undocumented alias for @samp{==} as opposed to an assignment operator,
 this usage is supported as a special case.  Be aware that a future
@@ -6686,16 +6688,15 @@ Eval
 extension when @acronym{POSIX} mode is not requested, and that using
 @samp{=} to check equality is not portable.
 
address@hidden status: 1
 @example
 eval(`2 = 2')
 @error{}m4:stdin:1: Warning: eval: recommend ==, not =, for equality
 @result{}1
 eval(`++0')
address@hidden:stdin:2: eval: invalid operator: ++0
address@hidden:stdin:2: Warning: eval: invalid operator: `++0'
 @result{}
 eval(`0 |= 1')
address@hidden:stdin:3: eval: invalid operator: 0 |= 1
address@hidden:stdin:3: Warning: eval: invalid operator: `0 |= 1'
 @result{}
 @end example
 
@@ -6738,12 +6739,12 @@ Eval
 eval(`2 || 1 / 0')
 @result{}1
 eval(`0 || 1 / 0')
address@hidden:stdin:9: Warning: eval: divide by zero: 0 || 1 / 0
address@hidden:stdin:9: Warning: eval: divide by zero: `0 || 1 / 0'
 @result{}
 eval(`0 && 1 % 0')
 @result{}0
 eval(`2 && 1 % 0')
address@hidden:stdin:11: Warning: eval: modulo by zero: 2 && 1 % 0
address@hidden:stdin:11: Warning: eval: modulo by zero: `2 && 1 % 0'
 @result{}
 @end example
 
@@ -6751,7 +6752,8 @@ Eval
 As a @acronym{GNU} extension, the operator @samp{**} performs integral
 exponentiation.  The operator is right-associative, and if evaluated,
 the exponent must be non-negative, and at least one of the arguments
-must be non-zero, or a warning is issued.
+must be non-zero, or a warning is issued.  Also, the C operator
address@hidden:} is supported.
 
 @example
 eval(`2 ** 3 ** 2')
@@ -6764,10 +6766,16 @@ Eval
 @result{}1
 eval(`0 ** 0')
 @result{}
address@hidden:stdin:5: Warning: eval: divide by zero: 0 ** 0
address@hidden:stdin:5: Warning: eval: divide by zero: `0 ** 0'
 eval(`4 ** -2')
address@hidden:stdin:6: Warning: eval: negative exponent: 4 ** -2
address@hidden:stdin:6: Warning: eval: negative exponent: `4 ** -2'
 @result{}
+eval(`0 ? 2 : 3')
address@hidden
+eval(`1 ? 2 : 1/0')
address@hidden
+eval(`0 ? 1/0 : 3')
address@hidden
 @end example
 
 Within @var{expression}, (but not @var{radix} or @var{width}), numbers
@@ -6811,7 +6819,7 @@ Eval
 define(`foo', `666')
 @result{}
 eval(`foo / 6')
address@hidden:stdin:11: Warning: eval: bad expression: foo / 6
address@hidden:stdin:11: Warning: eval: bad expression: `foo / 6'
 @result{}
 eval(foo / 6)
 @result{}111
@@ -8254,7 +8262,7 @@ Improved forloop
 forloop(`i', `5 + 5', `0xc', ` 0x`'eval(i, `16')')
 @result{} 0xa 0xb 0xc
 forloop(`i', `a', `b', `non-numeric bounds')
address@hidden:stdin:6: Warning: eval: bad expression (bad input): (a) <= (b)
address@hidden:stdin:6: Warning: eval: bad input: `(a) <= (b)'
 @result{}
 @end example
 
diff --git a/src/eval.c b/src/eval.c
index 1b617ed..c5ad30d 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -39,6 +39,7 @@ typedef enum eval_token
     LNOT, LAND, LOR,
     NOT, AND, OR, XOR,
     LEFTP, RIGHTP,
+    QUESTION, COLON,
     NUMBER, EOTEXT
   }
 eval_token;
@@ -56,6 +57,7 @@ typedef enum eval_error
        about a syntax error.  */
     SYNTAX_ERROR,
     MISSING_RIGHT,
+    MISSING_COLON,
     UNKNOWN_INPUT,
     EXCESS_INPUT,
     INVALID_OPERATOR,
@@ -63,6 +65,7 @@ typedef enum eval_error
   }
 eval_error;
 
+static eval_error condition_term (const call_info *, eval_token, int32_t *);
 static eval_error logical_or_term (const call_info *, eval_token, int32_t *);
 static eval_error logical_and_term (const call_info *, eval_token, int32_t *);
 static eval_error or_term (const call_info *, eval_token, int32_t *);
@@ -283,6 +286,10 @@ eval_lex (int32_t *val)
       return LEFTP;
     case ')':
       return RIGHTP;
+    case '?':
+      return QUESTION;
+    case ':':
+      return COLON;
     default:
       return ERROR;
     }
@@ -303,7 +310,7 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
   if (et == EOTEXT)
     err = EMPTY_ARGUMENT;
   else
-    err = logical_or_term (me, et, val);
+    err = condition_term (me, et, val);
 
   if (err == NO_ERROR && *eval_text != '\0')
     {
@@ -313,6 +320,8 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
        err = EXCESS_INPUT;
     }
 
+  if (err != NO_ERROR)
+    expr = quotearg_style_mem (locale_quoting_style, expr, len);
   switch (err)
     {
       /* Cases where result is printed.  */
@@ -325,8 +334,11 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
 
       /* Cases where error makes result meaningless.  */
     case MISSING_RIGHT:
-      m4_warn (0, me, _("bad expression (missing right parenthesis): %s"),
-              expr);
+      m4_warn (0, me, _("missing right parenthesis: %s"), expr);
+      break;
+
+    case MISSING_COLON:
+      m4_warn (0, me, _("missing colon: %s"), expr);
       break;
 
     case SYNTAX_ERROR:
@@ -334,15 +346,15 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
       break;
 
     case UNKNOWN_INPUT:
-      m4_warn (0, me, _("bad expression (bad input): %s"), expr);
+      m4_warn (0, me, _("bad input: %s"), expr);
       break;
 
     case EXCESS_INPUT:
-      m4_warn (0, me, _("bad expression (excess input): %s"), expr);
+      m4_warn (0, me, _("excess input: %s"), expr);
       break;
 
     case INVALID_OPERATOR:
-      m4_error (0, 0, me, _("invalid operator: %s"), expr);
+      m4_warn (0, me, _("invalid operator: %s"), expr);
       break;
 
     case DIVIDE_ZERO:
@@ -370,6 +382,55 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
 `---------------------------*/
 
 static eval_error
+condition_term (const call_info *me, eval_token et, int32_t *v1)
+{
+  int32_t v2;
+  int32_t v3;
+  eval_error er;
+
+  if ((er = logical_or_term (me, et, v1)) != NO_ERROR)
+    return er;
+
+  if ((et = eval_lex (&v2)) == QUESTION)
+    {
+      et = eval_lex (&v2);
+      if (et == ERROR)
+       return UNKNOWN_INPUT;
+
+      /* Implement short-circuiting of valid syntax.  */
+      /* C requires 'logical_or_term ? expression : condition_term';
+        if we ever introduce assignment_term or comma_term, then
+        condition_term and expression are no longer synonymous.  */
+      er = condition_term (me, et, &v2);
+      if (er != NO_ERROR
+         && !(*v1 == 0 && er < SYNTAX_ERROR))
+       return er;
+
+      et = eval_lex (&v3);
+      if (et == ERROR)
+       return UNKNOWN_INPUT;
+      if (et != COLON)
+       return MISSING_COLON;
+
+      et = eval_lex (&v3);
+      if (et == ERROR)
+       return UNKNOWN_INPUT;
+
+      er = condition_term (me, et, &v3);
+      if (er != NO_ERROR
+         && !(*v1 != 0 && er < SYNTAX_ERROR))
+       return er;
+
+      *v1 = *v1 ? v2 : v3;
+    }
+  if (et == ERROR)
+    return UNKNOWN_INPUT;
+
+  eval_undo ();
+  return NO_ERROR;
+}
+
+static eval_error
 logical_or_term (const call_info *me, eval_token et, int32_t *v1)
 {
   int32_t v2;
@@ -832,7 +893,7 @@ simple_term (const call_info *me, eval_token et, int32_t 
*v1)
       if (et == ERROR)
        return UNKNOWN_INPUT;
 
-      if ((er = logical_or_term (me, et, v1)) != NO_ERROR)
+      if ((er = condition_term (me, et, v1)) != NO_ERROR)
        return er;
 
       et = eval_lex (&v2);
-- 
1.6.0.4


From: Eric Blake <address@hidden>
Date: Wed, 24 Dec 2008 14:34:06 -0700
Subject: [PATCH] Relax eval as allowed by POSIX 2008.

* modules/evalparse.c (m4_evaluate): Warn, not error, on invalid
operator.  Quote expression in warning.
* modules/mpeval.c (includes): Add quotearg.h.
* doc/m4.texinfo (Eval, Improved forloop): Update tests.
* NEWS: Update to reflect 1.6 support for `?:'.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog           |    9 +++++++++
 NEWS                |    6 +++++-
 doc/m4.texinfo      |   19 +++++++++----------
 modules/evalparse.c |    5 +++--
 modules/mpeval.c    |    2 ++
 5 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 4321384..9d1c1c8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2008-12-24  Eric Blake  <address@hidden>
+
+       Relax eval as allowed by POSIX 2008.
+       * modules/evalparse.c (m4_evaluate): Warn, not error, on invalid
+       operator.  Quote expression in warning.
+       * modules/mpeval.c (includes): Add quotearg.h.
+       * doc/m4.texinfo (Eval, Improved forloop): Update tests.
+       * NEWS: Update to reflect 1.6 support for `?:'.
+
 2008-12-23  Eric Blake  <address@hidden>
 
        Add debugmode(o) to control dumpdef output location.
diff --git a/NEWS b/NEWS
index 4180190..ca4e0b0 100644
--- a/NEWS
+++ b/NEWS
@@ -158,7 +158,7 @@ promoted to 2.0.
     first line to show the definition of the macro being expanded.
 
 *** The `eval' and `mpeval' builtins now support the following new
-    operators: `>>>', `\', `?:', and  `,'.
+    operators: `>>>', `\', and  `,'.
 
 *** The `maketemp' builtin now always warns that it is obsolete, even in GNU
     mode where it uses the same secure algorithm as `mkstemp', because of
@@ -296,6 +296,10 @@ promoted to 2.0.
    context of a macro name, rather than acting on the empty string.  This
    was already done for `define', `pushdef', `builtin', and `indir'.
 
+** Enhance the `eval' builtin to understand the `?:' operator, and
+   downgrade a failed parse due to an unknown operator from an error to a
+   warning.
+
 ** A number of portability improvements inherited from gnulib.
 
 * Noteworthy changes in Version 1.4.10b (2008-02-25) [beta]
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 3d039f6..c5e36dd 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -7506,7 +7506,6 @@ Eval
 implementations of @code{m4} require explicit parentheses to get the
 correct result:
 
address@hidden status: 1
 @example
 eval(`1 == 2 > 0')
 @result{}1
@@ -7523,23 +7522,23 @@ Eval
 eval(`+ + - ~ ! ~ 0')
 @result{}1
 eval(`++0')
address@hidden:stdin:8: eval: invalid operator: ++0
address@hidden:stdin:8: Warning: eval: invalid operator: `++0'
 @result{}
 eval(`1 = 1')
address@hidden:stdin:9: eval: invalid operator: 1 = 1
address@hidden:stdin:9: Warning: eval: invalid operator: `1 = 1'
 @result{}
 eval(`0 |= 1')
address@hidden:stdin:10: eval: invalid operator: 0 |= 1
address@hidden:stdin:10: Warning: eval: invalid operator: `0 |= 1'
 @result{}
 eval(`2 || 1 / 0')
 @result{}1
 eval(`0 || 1 / 0')
address@hidden:stdin:12: Warning: eval: divide by zero: 0 || 1 / 0
address@hidden:stdin:12: Warning: eval: divide by zero: `0 || 1 / 0'
 @result{}
 eval(`0 && 1 % 0')
 @result{}0
 eval(`2 && 1 % 0')
address@hidden:stdin:14: Warning: eval: modulo by zero: 2 && 1 % 0
address@hidden:stdin:14: Warning: eval: modulo by zero: `2 && 1 % 0'
 @result{}
 @end example
 
@@ -7567,9 +7566,9 @@ Eval
 @result{}1
 eval(`0 ** 0')
 @result{}
address@hidden:stdin:5: Warning: eval: divide by zero: 0 ** 0
address@hidden:stdin:5: Warning: eval: divide by zero: `0 ** 0'
 eval(`4 ** -2')
address@hidden:stdin:6: Warning: eval: negative exponent: 4 ** -2
address@hidden:stdin:6: Warning: eval: negative exponent: `4 ** -2'
 @result{}
 eval(`2 || 4 ** -2')
 @result{}1
@@ -7632,7 +7631,7 @@ Eval
 define(`foo', `666')
 @result{}
 eval(`foo / 6')
address@hidden:stdin:11: Warning: eval: bad expression: foo / 6
address@hidden:stdin:11: Warning: eval: bad expression: `foo / 6'
 @result{}
 eval(foo / 6)
 @result{}111
@@ -9317,7 +9316,7 @@ Improved forloop
 forloop(`i', `5 + 5', `0xc', ` 0x`'eval(i, `16')')
 @result{} 0xa 0xb 0xc
 forloop(`i', `a', `b', `non-numeric bounds')
address@hidden:stdin:6: Warning: eval: bad input: (a) <= (b)
address@hidden:stdin:6: Warning: eval: bad input: `(a) <= (b)'
 @result{}
 @end example
 
diff --git a/modules/evalparse.c b/modules/evalparse.c
index 9927e13..ac30cfe 100644
--- a/modules/evalparse.c
+++ b/modules/evalparse.c
@@ -940,6 +940,8 @@ m4_evaluate (m4 *context, m4_obstack *obs, size_t argc, 
m4_macro_args *argv)
        err = EXCESS_INPUT;
     }
 
+  if (err != NO_ERROR)
+    str = quotearg_style_mem (locale_quoting_style, str, M4ARGLEN (1));
   switch (err)
     {
     case NO_ERROR:
@@ -967,8 +969,7 @@ m4_evaluate (m4 *context, m4_obstack *obs, size_t argc, 
m4_macro_args *argv)
       break;
 
     case INVALID_OPERATOR:
-      /* POSIX requires an error here, unless XCU ERN 137 is approved.  */
-      m4_error (context, 0, 0, me, _("invalid operator: %s"), str);
+      m4_warn (context, 0, me, _("invalid operator: %s"), str);
       break;
 
     case DIVIDE_ZERO:
diff --git a/modules/mpeval.c b/modules/mpeval.c
index 63cd56a..2d63d6e 100644
--- a/modules/mpeval.c
+++ b/modules/mpeval.c
@@ -32,6 +32,8 @@
 #  include <gmp.h>
 #endif
 
+#include "quotearg.h"
+
 /* Rename exported symbols for dlpreload()ing.  */
 #define m4_builtin_table       mpeval_LTX_m4_builtin_table
 #define m4_macro_table         mpeval_LTX_m4_macro_table
-- 
1.6.0.4







reply via email to

[Prev in Thread] Current Thread [Next in Thread]