I want to teach all other builtins that parse numbers to accept
alternative bases when running in GNU mode; with the caveat that eval
MUST treat leading 0 as octal in expressions, but everything else
(including eval's optional base and width parameters) will treat
leading 0 as decimal and issue a warning.  I'll probably make the
leading zero warning optional under a debugmode letter, since there
are existing scripts that intentionally use "incr(0defn(`val'))" even
when `val' is not yet defined, but that will be a separate patch.  I
also want to teach m4 to understand the same radix spellings as bash
(ie. "36#az" as a synonym of "0r36:az"); bash compatibility also
demands supporting through base 64 (Z is 62, then @ and _ provide the
remaining two digits).

To progress towards that goal, this patch factors out numeric parsing
out of eval.c into builtin.c, so that the next patch can reuse it in
more places.  The new parser issues warnings directly, including a new
warning when overflow is encountered (another place where a separate
debugmode option might be nice).

FIXME: changelog
---
 doc/m4.texi   |   4 +-
 src/builtin.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++
 src/eval.c    | 126 +++++++++++++-------------------------------------
 src/m4.h      |   2 +
 4 files changed, 153 insertions(+), 97 deletions(-)

diff --git a/doc/m4.texi b/doc/m4.texi
index 9729d2ac..5080baf8 100644
--- a/doc/m4.texi
+++ b/doc/m4.texi
@@ -7541,10 +7541,10 @@ Eval
 @error{}m4:stdin:10: warning: eval: missing operand: '1+'
 @result{}
 eval(`0x')
-@error{}m4:stdin:11: warning: eval: invalid number: '0x'
+@error{}m4:stdin:11: warning: eval: missing digit after radix '0x'
 @result{}
 eval(`01239')
-@error{}m4:stdin:12: warning: eval: invalid number: '01239'
+@error{}m4:stdin:12: warning: eval: invalid digit for radix 8: '01239'
 @result{}
 define(`foo', `666')
 @result{}
diff --git a/src/builtin.c b/src/builtin.c
index e8802610..b26d2b77 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -534,6 +534,124 @@ bad_argc (const call_info *name, int argc, unsigned int 
min, unsigned int max)
   return false;
 }

+/* The function parse_num () determines the numeric value starting at
+   ARG which must be at a digit, and setting *END to the first
+   non-digit.  Warn on behalf of NAME if invalid digits are
+   encountered, if the value overflows, or if there is a leading zero.
+   DECIMAL is true if a leading zero introduces decimal instead of
+   octal.  Update RESULT and return true on successful conversion.
+   The caller is responsible for processing any signs.  */
+bool
+parse_num (const call_info *name, const char *arg, bool decimal,
+           const char **end, uint32_t *result)
+{
+  unsigned base = 10;
+  unsigned digit;
+  uint32_t value = 0;
+  const char *start = arg;
+  bool leading_zero = false;
+  bool overflow = false;
+
+  if (*arg == '0')
+    {
+      switch (*++arg)
+        {
+        case 'x':
+        case 'X':
+          base = 16;
+          arg++;
+          break;
+
+        case 'b':
+        case 'B':
+          base = 2;
+          arg++;
+          break;
+
+        case 'r':
+        case 'R':
+          base = 0;
+          while (c_isdigit (*++arg) && base <= 36)
+            base = 10 * base + *arg - '0';
+          if (base == 0 || base > 36 || *arg != ':')
+            {
+              *end = ++arg;
+              m4_warn (0, name, _("invalid numeric base %s"),
+                       quotearg_style_mem (locale_quoting_style, start,
+                                           arg - start));
+              return false;
+            }
+          arg++;
+          break;
+
+        default:
+          leading_zero = true;
+          arg--;
+          if (!decimal)
+            base = 8;
+          break;
+        }
+
+      if (!c_isalnum (*arg))
+        {
+          *end = arg;
+          m4_warn (0, name, _("missing digit after radix %s"),
+                   quotearg_style_mem (locale_quoting_style, start,
+                                       arg - start));
+          return false;
+        }
+    }
+
+  for (; c_isalnum (*arg); arg++)
+    {
+      if (c_isdigit (*arg))
+        digit = *arg - '0';
+      else if (c_islower (*arg))
+        digit = *arg - 'a' + 10;
+      else
+        digit = *arg - 'A' + 10;
+
+      if (base == 1)
+        {
+          if (digit == 1)
+            value++;
+          else if (digit == 0 && value == 0)
+            continue;
+          else
+            {
+              *end = ++arg;
+              m4_warn (0, name, _("invalid digit for radix %u: %s"),
+                       base, quotearg_style_mem (locale_quoting_style, start,
+                                                 arg - start));
+              return false;
+            }
+        }
+      else if (digit >= base)
+        {
+          *end = ++arg;
+          m4_warn (0, name, _("invalid digit for radix %u: %s"),
+                   base, quotearg_style_mem (locale_quoting_style, start,
+                                             arg - start));
+          return false;
+        }
+      else
+        {
+          if (value * base + digit < value)
+            overflow = true;
+          value = value * base + digit;
+        }
+    }
+  if (leading_zero && (value || overflow))
+    m4_warn (0, name, _("leading 0 treated as base %u: %s"), base,
+             quotearg_style_mem (locale_quoting_style, start, arg - start));
+  if (overflow)
+    m4_warn (0, name, _("numeric overflow detected: %s"),
+             quotearg_style_mem (locale_quoting_style, start, arg - start));
+  *end = arg;
+  *result = value;
+  return true;
+}
+
 /* The function numeric_arg () converts ARG of length LEN to an int
    pointed to by VALUEP.  If the conversion fails, print error message
    on behalf of NAME.  Return true iff conversion succeeds.  */
diff --git a/src/eval.c b/src/eval.c
index 18cae9e2..2ae980a7 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -92,8 +92,9 @@ typedef enum eval_error
 }
 eval_error;

-static eval_error primary (int32_t *);
-static eval_error parse_expr (int32_t *, eval_error, unsigned);
+static eval_error primary (const call_info *, int32_t *);
+static eval_error parse_expr (const call_info *, int32_t *, eval_error,
+                              unsigned);

 /* Lexical functions.  */

@@ -125,7 +126,7 @@ eval_undo (void)
 /* VAL is numerical value, if any.  */

 static eval_token
-eval_lex (int32_t *val)
+eval_lex (const call_info *name, int32_t *val)
 {
   while (eval_text != end_text && c_isspace (*eval_text))
     eval_text++;
@@ -140,79 +141,13 @@ eval_lex (int32_t *val)

   if (c_isdigit (*eval_text))
     {
-      unsigned int base, digit;
       /* The documentation says that "overflow silently results in wraparound".
          Therefore use an unsigned integer type to avoid undefined behaviour
          when parsing '-2147483648'.  */
-      uint32_t value;
-      bool seen_digit = false;
-
-      if (*eval_text == '0')
-        {
-          eval_text++;
-          switch (*eval_text)
-            {
-            case 'x':
-            case 'X':
-              base = 16;
-              eval_text++;
-              break;
-
-            case 'b':
-            case 'B':
-              base = 2;
-              eval_text++;
-              break;
-
-            case 'r':
-            case 'R':
-              base = 0;
-              eval_text++;
-              while (c_isdigit (*eval_text) && base <= 36)
-                base = 10 * base + *eval_text++ - '0';
-              if (base == 0 || base > 36 || *eval_text != ':')
-                return BADNUM;
-              eval_text++;
-              break;
-
-            default:
-              base = 8;
-              seen_digit = true;
-            }
-        }
-      else
-        base = 10;
-
-      value = 0;
-      for (; *eval_text; eval_text++, seen_digit = true)
-        {
-          if (c_isdigit (*eval_text))
-            digit = *eval_text - '0';
-          else if (c_islower (*eval_text))
-            digit = *eval_text - 'a' + 10;
-          else if (c_isupper (*eval_text))
-            digit = *eval_text - 'A' + 10;
-          else
-            break;
-
-          if (base == 1)
-            {
-              if (digit == 1)
-                value++;
-              else if (digit == 0 && value == 0)
-                continue;
-              else
-                return BADNUM;
-            }
-          else if (digit >= base)
-            return BADNUM;
-          else
-            value = value * base + digit;
-        }
+      uint32_t value = 0;
+      bool good = parse_num (name, eval_text, false, &eval_text, &value);
       *val = value;
-      if (!seen_digit)
-        return BADNUM;
-      return NUMBER;
+      return good ? NUMBER : BADNUM;
     }

   switch (*eval_text++)
@@ -330,12 +265,12 @@ eval_lex (int32_t *val)

 /* Parse `(expr)', unary operators, and numbers.  */
 static eval_error
-primary (int32_t *v1)
+primary (const call_info *name, int32_t *v1)
 {
   eval_error er;
   int32_t v2;

-  switch (eval_lex (v1))
+  switch (eval_lex (name, v1))
     {
       /* Number */
     case NUMBER:
@@ -343,11 +278,11 @@ primary (int32_t *v1)

       /* Parenthesis */
     case LEFTP:
-      er = primary (v1);
-      er = parse_expr (v1, er, MIN_PREC);
+      er = primary (name, v1);
+      er = parse_expr (name, v1, er, MIN_PREC);
       if (er >= SYNTAX_ERROR)
         return er;
-      switch (eval_lex (&v2))
+      switch (eval_lex (name, &v2))
         {
         case ERROR:
           return UNKNOWN_INPUT;
@@ -367,17 +302,17 @@ primary (int32_t *v1)
          unsigned to signed is a silent twos-complement
          wrap-around.  */
     case PLUS:
-      return primary (v1);
+      return primary (name, v1);
     case MINUS:
-      er = primary (v1);
+      er = primary (name, v1);
       *v1 = (int32_t) -(uint32_t) *v1;
       return er;
     case NOT:
-      er = primary (v1);
+      er = primary (name, v1);
       *v1 = ~*v1;
       return er;
     case LNOT:
-      er = primary (v1);
+      er = primary (name, v1);
       *v1 = *v1 == 0 ? 1 : 0;
       return er;

@@ -400,7 +335,8 @@ primary (int32_t *v1)

 /* Parse binary operators with at least MIN_PREC precedence.  */
 static eval_error
-parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
+parse_expr (const call_info *name, int32_t *v1, eval_error er,
+            unsigned min_prec)
 {
   eval_token et;
   eval_token et2;
@@ -414,12 +350,12 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)

   if (er >= SYNTAX_ERROR)
     return er;
-  et = eval_lex (&v2);
+  et = eval_lex (name, &v2);
   while (et / 10 >= min_prec)
     {
       if (et == QUESTION)
         {
-          et2 = eval_lex (&v2);
+          et2 = eval_lex (name, &v2);
           eval_undo ();
           if (et2 == COLON)
             {
@@ -427,21 +363,21 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
               er2 = er;
             }
           else
-            er2 = primary (&v2);
+            er2 = primary (name, &v2);
         }
       else
-        er2 = primary (&v2);
+        er2 = primary (name, &v2);
       if (er2 >= SYNTAX_ERROR)
         return er2;
-      et2 = eval_lex (&v3);
+      et2 = eval_lex (name, &v3);
       /* Handle binary operators of higher precedence or right-associativity */
       while (et2 / 10 > et / 10 || et2 == EXPONENT ||
              (et == QUESTION && et2 == QUESTION))
         {
           eval_undo ();
-          if ((er2 = parse_expr (&v2, er2, et2 / 10)) >= SYNTAX_ERROR)
+          if ((er2 = parse_expr (name, &v2, er2, et2 / 10)) >= SYNTAX_ERROR)
             return er2;
-          et2 = eval_lex (&v3);
+          et2 = eval_lex (name, &v3);
         }
       /* Reduce the two values by the given binary operator */
       switch (et)
@@ -570,8 +506,8 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
             er = MISSING_COLON;
           else
             {
-              er3 = primary (&v3);
-              er3 = parse_expr (&v3, er3, MIN_PREC);
+              er3 = primary (name, &v3);
+              er3 = parse_expr (name, &v3, er3, MIN_PREC);
               if (er3 >= SYNTAX_ERROR)
                 return er3;
               if (*v1)
@@ -604,12 +540,12 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
   eval_error err;

   eval_init_lex (expr, len);
-  err = primary (val);
-  err = parse_expr (val, err, MIN_PREC);
+  err = primary (me, val);
+  err = parse_expr (me, val, err, MIN_PREC);

   if (err == NO_ERROR && eval_text != end_text)
     {
-      switch (eval_lex (val))
+      switch (eval_lex (me, val))
         {
         case BADNUM:
           err = INVALID_NUMBER;
@@ -656,7 +592,7 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
       break;

     case INVALID_NUMBER:
-      m4_warn (0, me, _("invalid number: %s"), expr);
+      /* parse_num already warned */
       break;

     case INVALID_OPERATOR:
diff --git a/src/m4.h b/src/m4.h
index 5eb02962..4e43a212 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -528,6 +528,8 @@ struct re_registers;

 extern void builtin_init (void);
 extern bool bad_argc (const call_info *, int, unsigned int, unsigned int);
+extern bool parse_num (const call_info *, const char *, bool, const char **,
+                       uint32_t *);
 extern void define_builtin (const char *, size_t, const builtin *,
                             symbol_lookup);
 extern void set_macro_sequence (const char *);
-- 
2.49.0


_______________________________________________
M4-patches mailing list
M4-patches@gnu.org
https://lists.gnu.org/mailman/listinfo/m4-patches

Reply via email to