TODO: Add more tests, fill in changelog
---

Given that POSIX says in one place that eval must operate on at least
32-bit numbers, and in another that it must use at least 'signed
long'; and given that most development machines these days are 64-bit,
I think it's high time M4 offers 64-bit math.  For back-compat
reasons, I'm thinking of keeping eval at 32-bit and adding a new
eval64 (the user can always use define(`eval', defn(`eval64')) to opt
in to the newer size).

But I'm not sure whether eval64 should be visible by default, or only
opt-in via a command line option, or even if I can get define(`eval',
builtin(`eval64')) working even when eval64 itself is not pre-defined.
So for now, I'm just posting what I got working in one evening, but it
is not yet the final patch.

Thoughts on the best way to add this feature?

 NEWS            |  3 ++-
 doc/m4.texi     |  9 +++++---
 src/Makefile.am |  2 +-
 src/builtin.c   | 37 ++++++++++++++++++++++++-------
 src/eval.c      | 58 +++++++++++++++++++++++++++++--------------------
 src/eval64.c    | 39 +++++++++++++++++++++++++++++++++
 src/m4.h        |  9 ++++++--
 7 files changed, 118 insertions(+), 39 deletions(-)
 create mode 100644 src/eval64.c

diff --git a/NEWS b/NEWS
index 97c4d9c0..f6204217 100644
--- a/NEWS
+++ b/NEWS
@@ -129,7 +129,8 @@ GNU M4 NEWS - User visible changes.
 ** Enhance the `eval' builtin to understand the `?:' operator, and
    downgrade a failed parse due to an unknown operator from an error to a
    warning.  Further, the builtin now refuses to recognize `=' as a
-   synonym for `==' (this had emitted a warning since 1.4.8b).
+   synonym for `==' (this had emitted a warning since 1.4.8b).  Add a new
+   `eval64' builtin that operates on 64-bit integers.

 ** A number of portability improvements inherited from gnulib.

diff --git a/doc/m4.texi b/doc/m4.texi
index 81476bc8..60992c5f 100644
--- a/doc/m4.texi
+++ b/doc/m4.texi
@@ -7070,13 +7070,16 @@ Eval
 Integer expressions are evaluated with @code{eval}:

 @deffn Builtin eval (@var{expression}, @dvar{radix, 10}, @ovar{width})
+@deffnx Builtin eval64 (@var{expression}, @dvar{radix, 10}, @ovar{width})
 Expands to the value of @var{expression}.  The expansion is empty
 if a problem is encountered while parsing the arguments.  If specified,
 @var{radix} and @var{width} control the format of the output.

-Calculations are done with 32-bit signed numbers.  Overflow silently
-results in wraparound.  A warning is issued if division by zero is
-attempted, or if @var{expression} could not be parsed.
+Calculations are done with 32-bit signed numbers for @code{eval}, and
+64-bit signed numbers for @code{eval64} (the latter was introduced in M4
+1.6).  Overflow silently results in wraparound.  A warning is issued if
+division by zero is attempted, or if @var{expression} could not be
+parsed.

 Expressions can contain the following operators, listed in order of
 decreasing precedence.
diff --git a/src/Makefile.am b/src/Makefile.am
index 0f567012..08f48afe 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -26,7 +26,7 @@ AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS)
 AM_LDFLAGS = $(OS2_LDFLAGS)
 bin_PROGRAMS = m4
 noinst_HEADERS = m4.h
-m4_SOURCES = m4.c builtin.c debug.c eval.c format.c freeze.c input.c \
+m4_SOURCES = m4.c builtin.c debug.c eval.c eval64.c format.c freeze.c input.c \
 macro.c output.c path.c symtab.c
 LDADD = ../lib/libm4.a $(LIBM4_LIBDEPS) \
   $(CLOCK_TIME_LIB) $(GETLOCALENAME_L_LIB) $(GETRANDOM_LIB) \
diff --git a/src/builtin.c b/src/builtin.c
index bf0d1798..bba80e33 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -57,6 +57,7 @@ DECLARE (m4_dumpdef);
 DECLARE (m4_errprint);
 DECLARE (m4_esyscmd);
 DECLARE (m4_eval);
+DECLARE (m4_eval64);
 DECLARE (m4_format);
 DECLARE (m4_ifdef);
 DECLARE (m4_ifelse);
@@ -109,6 +110,7 @@ static builtin const builtin_tab[] = {
   {"errprint", false, false, true, m4_errprint},
   {"esyscmd", true, false, true, m4_esyscmd},
   {"eval", false, false, true, m4_eval},
+  {"eval64", true, false, true, m4_eval64},
   {"format", true, false, true, m4_format},
   {"ifdef", false, true, true, m4_ifdef},
   {"ifelse", false, true, true, m4_ifelse},
@@ -571,10 +573,10 @@ static char const digits[] = 
"0123456789abcdefghijklmnopqrstuvwxyz";
 /* The function ntoa () converts VALUE to a signed ASCII
    representation in radix RADIX, with the ending \0 in *END.  */
 static const char *
-ntoa (int32_t value, int radix, const char **end)
+ntoa (int64_t value, int radix, const char **end)
 {
   bool negative;
-  uint32_t uvalue;
+  uint64_t uvalue;
   /* Sized for radix 2, plus sign and trailing NUL.  */
   static char str[sizeof (value) * CHAR_BIT + 2];
   char *s = &str[sizeof str];
@@ -585,12 +587,12 @@ ntoa (int32_t value, int radix, const char **end)
   if (value < 0)
     {
       negative = true;
-      uvalue = -(uint32_t) value;
+      uvalue = -(uint64_t) value;
     }
   else
     {
       negative = false;
-      uvalue = (uint32_t) value;
+      uvalue = (uint64_t) value;
     }

   do
@@ -1253,10 +1255,11 @@ m4_sysval (struct obstack *obs, int argc MAYBE_UNUSED,
    The actual work is done in the function evaluate (), which lives in
    eval.c.  */
 static void
-m4_eval (struct obstack *obs, int argc, macro_arguments *argv)
+eval (struct obstack *obs, int argc, macro_arguments *argv,
+      bool func (const call_info *, const char *, size_t, int64_t *))
 {
   const call_info *me = arg_info (argv);
-  int32_t value = 0;
+  int64_t value = 0;
   int radix = 10;
   int min = 1;
   const char *s;
@@ -1283,17 +1286,23 @@ m4_eval (struct obstack *obs, int argc, macro_arguments 
*argv)
       return;
     }

-  if (evaluate (me, ARG (1), ARG_LEN (1), &value))
+  if (func (me, ARG (1), ARG_LEN (1), &value))
     return;

   if (radix == 1)
     {
+      if (value < INT_MIN || value > INT_MAX)
+        {
+          m4_warn (0, me, _("magnitude too large for base 1: %" PRId64),
+                   value);
+          return;
+        }
       if (value < 0)
         {
           obstack_1grow (obs, '-');
           value = -value;
         }
-      if (value + 0U < min + 0U)
+      if (value + 0ULL < min + 0ULL)
         {
           obstack_blank (obs, min - value);
           memset ((char *) obstack_next_free (obs) - (min - value), '0',
@@ -1321,6 +1330,18 @@ m4_eval (struct obstack *obs, int argc, macro_arguments 
*argv)
   obstack_grow (obs, s, len);
 }

+static void
+m4_eval (struct obstack *obs, int argc, macro_arguments *argv)
+{
+  eval (obs, argc, argv, evaluate);
+}
+
+static void
+m4_eval64 (struct obstack *obs, int argc, macro_arguments *argv)
+{
+  eval (obs, argc, argv, evaluate64);
+}
+
 static void
 m4_incr (struct obstack *obs, int argc, macro_arguments *argv)
 {
diff --git a/src/eval.c b/src/eval.c
index 163e2634..ae98a03f 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -22,9 +22,16 @@
 /* This file contains the functions to evaluate integer expressions for
    the "eval" macro.  It is a little, fairly self-contained module, with
    its own scanner, and a recursive descent parser.  The only entry point
-   is evaluate ().  */
+   is evaluate ().  This file is also used to implement eval64.  */

-#include "m4.h"
+#ifndef EVAL64
+# include "m4.h"
+
+# define INT int32_t
+# define UINT uint32_t
+# define SHIFTMASK 0x1f
+# define EVALUATE evaluate
+#endif /* !EVAL64 */

 /* Evaluates token types.  */

@@ -87,8 +94,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 (INT *);
+static eval_error parse_expr (INT *, eval_error, unsigned);

 /* Lexical functions.  */

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

 static eval_token
-eval_lex (int32_t *val)
+eval_lex (INT *val)
 {
   while (eval_text != end_text && c_isspace (*eval_text))
     eval_text++;
@@ -139,7 +147,7 @@ eval_lex (int32_t *val)
       /* 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;
+      UINT value;

       if (*eval_text == '0')
         {
@@ -314,10 +322,10 @@ eval_lex (int32_t *val)

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

   switch (eval_lex (v1))
     {
@@ -352,7 +360,7 @@ primary (int32_t *v1)
       return primary (v1);
     case MINUS:
       er = primary (v1);
-      *v1 = (int32_t) -(uint32_t) *v1;
+      *v1 = (INT) -(UINT) *v1;
       return er;
     case NOT:
       er = primary (v1);
@@ -380,15 +388,15 @@ 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 (INT *v1, eval_error er, unsigned min_prec)
 {
   eval_token et;
   eval_token et2;
   eval_error er2;
   eval_error er3;
-  int32_t v2;
-  int32_t v3;
-  uint32_t u1;
+  INT v2;
+  INT v3;
+  UINT u1;

   if (er >= SYNTAX_ERROR)
     return er;
@@ -437,20 +445,20 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
           else
             {
               while (v2-- > 0)
-                u1 *= (uint32_t) *v1;
+                u1 *= (UINT) *v1;
             }
           *v1 = u1;
           break;

         case TIMES:
-          *v1 = (int32_t) ((uint32_t) *v1 * (uint32_t) v2);
+          *v1 = (INT) ((UINT) *v1 * (UINT) v2);
           break;
         case DIVIDE:
           if (v2 == 0)
             er = DIVIDE_ZERO;
           else if (v2 == -1)
             /* Avoid overflow, and the x86 SIGFPE on INT_MIN / -1.  */
-            *v1 = (int32_t) -(uint32_t) *v1;
+            *v1 = (INT) -(UINT) *v1;
           else
             *v1 /= v2;
           break;
@@ -465,20 +473,20 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
           break;

         case PLUS:
-          *v1 = (int32_t) ((uint32_t) *v1 + (uint32_t) v2);
+          *v1 = (INT) ((UINT) *v1 + (UINT) v2);
           break;
         case MINUS:
-          *v1 = (int32_t) ((uint32_t) *v1 - (uint32_t) v2);
+          *v1 = (INT) ((UINT) *v1 - (UINT) v2);
           break;

         case LSHIFT:
           u1 = *v1;
-          u1 <<= (uint32_t) (v2 & 0x1f);
+          u1 <<= (UINT) (v2 & SHIFTMASK);
           *v1 = u1;
           break;
         case RSHIFT:
           u1 = *v1 < 0 ? ~*v1 : *v1;
-          u1 >>= (uint32_t) (v2 & 0x1f);
+          u1 >>= (UINT) (v2 & SHIFTMASK);
           *v1 = *v1 < 0 ? ~u1 : u1;
           break;

@@ -563,17 +571,18 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)

 /* Main entry point, called from "eval".  */
 bool
-evaluate (const call_info *me, const char *expr, size_t len, int32_t *val)
+EVALUATE (const call_info *me, const char *expr, size_t len, int64_t *pval)
 {
   eval_error err;
+  INT val;

   eval_init_lex (expr, len);
-  err = primary (val);
-  err = parse_expr (val, err, 1);
+  err = primary (&val);
+  err = parse_expr (&val, err, 1);

   if (err == NO_ERROR && eval_text != end_text)
     {
-      if (eval_lex (val) == BADOP)
+      if (eval_lex (&val) == BADOP)
         err = INVALID_OPERATOR;
       else
         err = EXCESS_INPUT;
@@ -585,6 +594,7 @@ evaluate (const call_info *me, const char *expr, size_t 
len, int32_t *val)
     {
       /* Cases where result is printed.  */
     case NO_ERROR:
+      *pval = val;
       return false;

     case EMPTY_ARGUMENT:
diff --git a/src/eval64.c b/src/eval64.c
new file mode 100644
index 00000000..6d5ae1b7
--- /dev/null
+++ b/src/eval64.c
@@ -0,0 +1,39 @@
+/* GNU m4 -- A simple macro processor
+
+   Copyright (C) 1989-1994, 2006-2014, 2016-2017, 2020-2025 Free
+   Software Foundation, Inc.
+
+   This file is part of GNU M4.
+
+   GNU M4 is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   GNU M4 is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+/* This file contains the functions to evaluate 64-bit integer
+   expressions for the "eval64" macro, by reusing code from eval.  The
+   only entry point is evaluate64 ().  */
+
+#include "m4.h"
+
+#define EVAL64
+#define INT int64_t
+#define UINT uint64_t
+#define SHIFTMASK 0x3f
+#define EVALUATE evaluate64
+
+#include "eval.c"
+/*
+  Defined by eval.c:
+bool
+evaluate64 (const call_info *me, const char *expr, size_t len, INT *val)
+ */
diff --git a/src/m4.h b/src/m4.h
index 79dca438..c1735318 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -29,6 +29,7 @@
 #include <c-ctype.h>
 #include <errno.h>
 #include <error.h>
+#include <inttypes.h>
 #include <limits.h>
 #include <locale.h>
 #include <stdbool.h>
@@ -546,9 +547,13 @@ extern void include_env_init (void);
 extern void add_include_directory (const char *);
 extern FILE *m4_path_search (const char *, bool, char **);
 
-/* File: eval.c  --- expression evaluation.  */
+/* File: eval.c  --- 32-bit expression evaluation.  */

-extern bool evaluate (const call_info *, const char *, size_t, int32_t *);
+extern bool evaluate (const call_info *, const char *, size_t, int64_t *);
+
+/* File: eval64.c  --- 64-bit expression evaluation.  */
+
+extern bool evaluate64 (const call_info *, const char *, size_t, int64_t *);
 
 /* File: format.c  --- printf like formatting.  */


base-commit: de2ad6ddc904ea07fe9a6f61fa9418b15ffbfc0b
prerequisite-patch-id: 609f7f2d1044633fdedb9052c0367b1d7b7b8cce
prerequisite-patch-id: bcbc80d445cc1737da3b453d50426c9f944186c9
-- 
2.49.0


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

Reply via email to