[PATCH 3/3] Introduce $(compare ...) for numerical comparison

2021-07-16 Thread Jouke Witteveen
Numbers can come from $(words ...), automatic variables such as
$(MAKELEVEL), from environment variables, or from shell output such as
through $(shell expr ...).  The $(compare ...) function allows
conditional evaluation controlled by numerical variables.

* NEWS: Announce this feature.
* doc/make.texi (Functions for Conditionals): Document 'compare'.
* src/function.c (func_compare): Create the 'compare' built-in function.
* tests/scripts/functions/compare: Test the 'compare' built-in function.
---

This is a cleaned-up and documented version of a proposal submitted a
year ago. The interface was conceived by Edward Welbourne and myself.
Personally, I would not introduce mathematical operators to make, but
given that numbers are a thing and they do pop up in variables, a
comparison function makes sense to me.

Thanks for considering!
- Jouke

 NEWS|  4 +++
 doc/make.texi   | 36 -
 src/function.c  | 46 ++
 tests/scripts/functions/compare | 57 +
 4 files changed, 142 insertions(+), 1 deletion(-)
 create mode 100644 tests/scripts/functions/compare

diff --git a/NEWS b/NEWS
index 5356260..6a3a744 100644
--- a/NEWS
+++ b/NEWS
@@ -40,6 +40,10 @@ 
https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=109&se
   user-defined function and they will not impact global variable assignments.
   Implementation provided by Jouke Witteveen 
 
+* New feature: The $(compare ...) function
+  This function allows conditional evaluation controlled by a numerical
+  comparison.
+
 * New debug option "print" will show the recipe to be run, even when silent
   mode is set.
 
diff --git a/doc/make.texi b/doc/make.texi
index 1ebf6ae..14a59f6 100644
--- a/doc/make.texi
+++ b/doc/make.texi
@@ -7654,7 +7654,7 @@ the file names to refer to an existing file or directory. 
 Use the
 @section Functions for Conditionals
 @findex if
 @cindex conditional expansion
-There are three functions that provide conditional expansion.  A key
+There are four functions that provide conditional expansion.  A key
 aspect of these functions is that not all of the arguments are
 expanded initially.  Only those arguments which need to be expanded,
 will be expanded.
@@ -7701,6 +7701,32 @@ empty string the processing stops and the result of the 
expansion is
 the empty string.  If all arguments expand to a non-empty string then
 the result of the expansion is the expansion of the last argument.
 
+@item $(compare 
@var{lhs},@var{rhs},@var{lt-part}[,@var{eq-part}[,@var{gt-part}]])
+@findex compare
+The @code{compare} function provides support for numerical comparison of
+integers.  This function has no counterpart among the GNU @code{make}
+makefile conditionals.
+
+The left-hand side, @var{lhs}, and right-hand side, @var{rhs}, are
+expanded and parsed as integral numbers in base 10.  Expansion of the
+remaining arguments is controlled by how the numerical left-hand side
+compares to the numerical right-hand side.
+
+If the left-hand side is strictly less than the right-hand side, the
+entire @code{compare} function evaluates to the expansion of the third
+argument, @var{lt-part}.  If both sides compare equal, then the fourth
+argument, @var{eq-part}, is expanded and becomes the result of the
+@code{compare} function.  If the left-hand side is strictly greater than
+the right-hand side, then the @code{compare} function evaluates to the
+expansion of the fifth argument, @var{gt-part}.
+
+In case of missing arguments, @var{gt-part} defaults to @var{eq-part},
+and @var{eq-part} defaults to the empty string.  Thus both
+@samp{$(compare 9,7,hello)} and @samp{$(compare 9,7,hello,world,)}
+evaluate to the empty string, while @samp{$(compare 9,7,hello,world)}
+(notice the absence of a comma after @code{world}) evaluates to
+@samp{world}.
+
 @end table
 
 @node Let Function, Foreach Function, Conditional Functions, Functions
@@ -12523,6 +12549,14 @@ all expansions result in a non-empty string, 
substitute the expansion
 of the last @var{condition}.@*
 @xref{Conditional Functions, ,Functions for Conditionals}.
 
+@item $(compare 
@var{lhs},@var{rhs},@var{lt-part}[,@var{eq-part}[,@var{gt-part}]])
+Compare @var{lhs} and @var{rhs} numerically; substitute the expansion of
+@var{lt-part}, @var{eq-part}, or @var{gt-part} depending on whether the
+left-hand side is less-than, equal-to, or greater-than the right-hand
+side, respectively.  If @var{gt-part} is left out, it defaults to
+@var{eq-part}.@*
+@xref{Conditional Functions, ,Functions for Conditionals}.
+
 @item $(call @var{var},@var{param},@dots{})
 Evaluate the variable @var{var} replacing any references to @code{$(1)},
 @code{$(2)} with the first, second, etc.@: @var{param} values.@*
diff --git a/src/function.c b/src/function.c
index 964169a..587786a 100644
--- a/src/function.c
+++ b/src/function.c
@@ -1272,6 +1272,51 @@ func_sort (char *o, char **argv, const char *f

[PATCH 2/3] Use strtol instead of atoi

2021-07-16 Thread Jouke Witteveen
strtol is part of C89 and a fallback is provided by gnulib

* src/function.c (func_word, func_wordlist): Use strtol instead of atoi
* test/scripts/functions/word: Add out-of-range verification testing
---
 bootstrap.conf   |  1 +
 src/function.c   | 51 ++--
 tests/scripts/functions/word | 12 ++---
 3 files changed, 36 insertions(+), 28 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 7c4c9ab..c9c3668 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -50,4 +50,5 @@ findprog-in
 getloadavg
 host-cpu-c-abi
 strerror
+strtol
 make-glob"
diff --git a/src/function.c b/src/function.c
index 396f129..964169a 100644
--- a/src/function.c
+++ b/src/function.c
@@ -766,19 +766,24 @@ strip_whitespace (const char **begpp, const char **endpp)
   return (char *)*begpp;
 }
 
-static void
-check_numeric (const char *s, const char *msg)
+static long
+parse_numeric (const char *s, const char *msg)
 {
-  const char *end = s + strlen (s) - 1;
   const char *beg = s;
-  strip_whitespace (&s, &end);
-
-  for (; s <= end; ++s)
-if (!ISDIGIT (*s))  /* ISDIGIT only evals its arg once: see makeint.h.  */
-  break;
+  const char *end = s + strlen (s) - 1;
+  char *endp;
+  long num;
+  strip_whitespace (&beg, &end);
 
-  if (s <= end || end - beg < 0)
-OSS (fatal, *expanding_var, "%s: '%s'", msg, beg);
+  errno = 0;
+  num = strtol (beg, &endp, 10);
+  if (errno == ERANGE)
+OSS (fatal, *expanding_var, "%s: '%s'", strerror (errno), s);
+  else if (endp == beg || endp <= end)
+/* Empty or non-numeric input */
+OSS (fatal, *expanding_var, "%s: '%s'", msg, s);
+
+  return num;
 }
 
 
@@ -788,13 +793,11 @@ func_word (char *o, char **argv, const char *funcname 
UNUSED)
 {
   const char *end_p;
   const char *p;
-  int i;
+  long i;
 
-  /* Check the first argument.  */
-  check_numeric (argv[0], _("non-numeric first argument to 'word' function"));
-  i = atoi (argv[0]);
-
-  if (i == 0)
+  i = parse_numeric (argv[0],
+ _("non-numeric first argument to 'word' function"));
+  if (i <= 0)
 O (fatal, *expanding_var,
_("first argument to 'word' function must be greater than 0"));
 
@@ -812,20 +815,18 @@ func_word (char *o, char **argv, const char *funcname 
UNUSED)
 static char *
 func_wordlist (char *o, char **argv, const char *funcname UNUSED)
 {
-  int start, count;
+  long start, stop, count;
 
-  /* Check the arguments.  */
-  check_numeric (argv[0],
- _("non-numeric first argument to 'wordlist' function"));
-  check_numeric (argv[1],
- _("non-numeric second argument to 'wordlist' function"));
+  start = parse_numeric (argv[0],
+ _("non-numeric first argument to 'wordlist' 
function"));
+  stop = parse_numeric (argv[1],
+_("non-numeric second argument to 'wordlist' 
function"));
 
-  start = atoi (argv[0]);
   if (start < 1)
 ON (fatal, *expanding_var,
-"invalid first argument to 'wordlist' function: '%d'", start);
+"invalid first argument to 'wordlist' function: '%ld'", start);
 
-  count = atoi (argv[1]) - start + 1;
+  count = stop - start + 1;
 
   if (count > 0)
 {
diff --git a/tests/scripts/functions/word b/tests/scripts/functions/word
index 4dcc940..044bc94 100644
--- a/tests/scripts/functions/word
+++ b/tests/scripts/functions/word
@@ -51,6 +51,7 @@ run_make_test('FOO = foo bar biz baz
 word-e1: ; @echo $(word ,$(FOO))
 word-e2: ; @echo $(word abc ,$(FOO))
 word-e3: ; @echo $(word 1a,$(FOO))
+word-e4: ; @echo $(word 999,$(FOO))
 
 wordlist-e1: ; @echo $(wordlist ,,$(FOO))
 wordlist-e2: ; @echo $(wordlist abc ,,$(FOO))
@@ -69,19 +70,24 @@ run_make_test(undef,
   "#MAKEFILE#:5: *** non-numeric first argument to 'word' 
function: '1a'.  Stop.",
   512);
 
+run_make_test(undef,
+  'word-e4',
+  "#MAKEFILE#:6: *** Numerical result out of range: 
'999'.  Stop.",
+  512);
+
 run_make_test(undef,
   'wordlist-e1',
-  "#MAKEFILE#:7: *** non-numeric first argument to 'wordlist' 
function: ''.  Stop.",
+  "#MAKEFILE#:8: *** non-numeric first argument to 'wordlist' 
function: ''.  Stop.",
   512);
 
 run_make_test(undef,
   'wordlist-e2',
-  "#MAKEFILE#:8: *** non-numeric first argument to 'wordlist' 
function: 'abc '.  Stop.",
+  "#MAKEFILE#:9: *** non-numeric first argument to 'wordlist' 
function: 'abc '.  Stop.",
   512);
 
 run_make_test(undef,
   'wordlist-e3',
-  "#MAKEFILE#:9: *** non-numeric second argument to 'wordlist' 
function: ' 12a '.  Stop.",
+  "#MAKEFILE#:10: *** non-numeric second argument to 'wordlist' 
function: ' 12a '.  Stop.",
   512);
 
 # Test error conditions again, but this time in a variable reference
-- 
2.32.0




[PATCH 1/3] * src/makeint.h: Removed unused atol declaration

2021-07-16 Thread Jouke Witteveen
---
 src/makeint.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/makeint.h b/src/makeint.h
index fcfb7bd..ca6d49d 100644
--- a/src/makeint.h
+++ b/src/makeint.h
@@ -631,7 +631,6 @@ void spin (const char* suffix);
 
 #if !defined (__GNU_LIBRARY__) && !defined (POSIX) && !defined 
(_POSIX_VERSION) && !defined(WINDOWS32)
 
-long int atol ();
 # ifndef VMS
 long int lseek ();
 # endif
-- 
2.32.0