bug-make
[Top][All Lists]
Advanced

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

[PATCH] Add arithmetic builtin functions


From: Pete Dietl
Subject: [PATCH] Add arithmetic builtin functions
Date: Fri, 29 Nov 2024 04:14:16 -0800

Here I submit to you a patch that adds the following builtin functions
to make: `add` (addition), `sub` (subtraction), `mul`
(multiplication), `div` (division), `mod` (modulus), `max` (maximum),
`min` (minimum), and `abs` (absolute value). The implementation I
provide supports both integers and floats with promotions from integer
to float when appropriate (an operation where at least one argument is
a float causes the other argument to be promoted to a float and the
result to be a float).

I attempted to keep the operations behavior similar to how they behave
in Scheme.
Here are the function details:

Calling `add` with a single argument simply returns that argument;
`add` accepts 1+ arguments.
Calling `sub` with a single argument returns the negation of that
argument; `sub` accepts 1+ arguments.
Calling `mul` with a single argument simply returns that argument;
`mul` accepts 1+ arguments.
Calling `div` with a single argument returns the inverse of that
argument (i.e., `1/arg`); `div` accepts 1+ arguments. Furthermore, all
divisors are checked against being zero.
Calling `mod` requires that exactly two arguments are provided and
that both arguments are integers. Furthermore, the divisor is checked
against being zero.
Calling `max` with a single argument simply returns that argument;
`max` accepts 1+ arguments.
Calling `min` with a single argument simply returns that argument;
`max` accepts 1+ arguments.
Calling `abs` requires exactly one argument.

Please let me know what you all think.

>From 76db129404896c15440b405949b8c830e7ebb98f Mon Sep 17 00:00:00 2001
From: Pete Dietl <petedietl@gmail.com>
Date: Fri, 29 Nov 2024 03:57:06 -0800
Subject: [PATCH] Add arithmetic builtin functions

---
 src/function.c | 401 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 400 insertions(+), 1 deletion(-)

diff --git a/src/function.c b/src/function.c
index b4c38052..61cb7d5b 100644
--- a/src/function.c
+++ b/src/function.c
@@ -14,6 +14,10 @@ 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/>.  */

+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+
 #include "makeint.h"
 #include "filedef.h"
 #include "variable.h"
@@ -23,7 +27,6 @@ this program.  If not, see
<https://www.gnu.org/licenses/>.  */
 #include "commands.h"
 #include "debug.h"

-
 struct function_table_entry
   {
     union {
@@ -952,6 +955,394 @@ func_let (char *o, char **argv, const char
*funcname UNUSED)
   return o + strlen (o);
 }

+enum number_type
+{
+  t_integer,
+  t_floating
+};
+
+struct number
+{
+  enum number_type type;
+  union
+  {
+    double floating;
+    long long integer;
+  };
+};
+
+enum math_operation_init_type
+{
+  t_constant,
+  t_first_value,
+};
+
+struct math_operation_init
+{
+  enum math_operation_init_type init_type;
+  long long constant;
+};
+
+enum math_operation
+{
+  op_add,
+  op_subtract,
+  op_multiply,
+  op_max,
+  op_min,
+  op_abs,
+};
+
+/* Generic function for double */
+static inline double
+generic_math_op_double (double a, double b, enum math_operation op)
+{
+  switch (op)
+    {
+    case op_add:
+      return a + b;
+    case op_subtract:
+      return a - b;
+    case op_multiply:
+      return a * b;
+    case op_max:
+      return (a > b) ? a : b;
+    case op_min:
+      return (a < b) ? a : b;
+    case op_abs:
+      return fabs (a);
+    default:
+      /* Handle invalid operation */
+      return 0.0;
+    }
+}
+
+/* Generic function for long long int */
+static inline long long int
+generic_math_op_ll (long long int a, long long int b, enum math_operation op)
+{
+  switch (op)
+    {
+    case op_add:
+      return a + b;
+    case op_subtract:
+      return a - b;
+    case op_multiply:
+      return a * b;
+    case op_max:
+      return (a > b) ? a : b;
+    case op_min:
+      return (a < b) ? a : b;
+    case op_abs:
+      return llabs (a);
+    default:
+      /* Handle invalid operation */
+      return 0;
+    }
+}
+
+/* Generic macro to select the correct function based on type and
+   assert that the types of a and b are the same */
+#define generic_math_op(a, b, op)                                             \
+  ((void)sizeof (                                                             \
+       char[__builtin_types_compatible_p (typeof (a), typeof (b)) ? 1 : -1]), \
+   _Generic ((a),                                                             \
+       double: generic_math_op_double,                                        \
+       long long int: generic_math_op_ll) (a, b, op))
+
+static struct number
+parse_number (const char *s, const char *op_name)
+{
+  const char *beg = s;
+  const char *end = s + strlen (s) - 1;
+  char *endp;
+  struct number ret = { .integer = 0, .type = t_integer };
+
+  while (isspace(*s))
+    s++;
+
+  // We need to check this because even though we can command `strtoll` to
+  // parse only base-ten numbers, we can't command `strtod` to only parse
+  // base-10 numbers. Therefore, without this check `0xB` would get rejected by
+  // `strtoll`, but accepted by `strtod` and treated as `11.0`.
+  if (*s != '\0' && s[1] == 'x')
+    OSS (fatal, *expanding_var,
+         _ ("Invalid argument to %s function: '%s' not a number"), op_name, s);
+
+  errno = 0;
+  ret.integer = strtoll (beg, &endp, 10);
+  if (errno == ERANGE)
+    OSS (fatal, *expanding_var,
+         _ ("Invalid argument to %s function: '%s' out of range"), op_name, s);
+  if (endp == beg || endp <= end)
+    {
+      /* Empty or not an integer */
+      errno = 0;
+      ret.type = t_floating;
+      ret.floating = strtod (beg, &endp);
+      if (errno == ERANGE)
+        OSS (fatal, *expanding_var,
+             _ ("Invalid argument to %s function: '%s' out of range"), op_name,
+             s);
+      if (endp == beg || endp <= end)
+        OSS (fatal, *expanding_var,
+             _ ("Invalid argument to %s function: '%s'"), op_name, s);
+    }
+
+  return ret;
+}
+
+static char *
+number_to_string (char *o, struct number n)
+{
+  char buffer[64];
+  if (n.type == t_integer)
+    snprintf (buffer, sizeof buffer, "%lld", n.integer);
+  else
+    {
+      char *end;
+      snprintf (buffer, sizeof buffer, "%.14f", n.floating);
+      end = buffer + strlen (buffer) - 1;
+      while (end > buffer && *end == '0')
+        *end-- = '\0'; // Remove trailing zero
+
+      // If there's a decimal point left, append a '0' to keep a single zero
+      if (*end == '.')
+        {
+          end++;
+          *end++ = '0';
+          *end = '\0';
+        }
+    }
+  return variable_buffer_output (o, buffer, strlen (buffer));
+}
+
+struct number
+perform_math_op (enum math_operation op, const char *op_name,
+                 struct math_operation_init init, char **argv)
+{
+  struct number parsed;
+  struct number total;
+
+  if (init.init_type == t_first_value)
+    {
+      assert (*argv != NULL);
+
+      total = parse_number (*argv, op_name);
+      argv++;
+    }
+  else
+    {
+      total.type = t_integer;
+      total.integer = init.constant;
+    }
+
+  for (; *argv != NULL; argv++)
+    {
+      parsed = parse_number (*argv, op_name);
+      if (total.type == t_integer)
+        {
+          if (parsed.type == t_integer)
+            total.integer
+                = generic_math_op (total.integer, parsed.integer, op);
+          else // `parsed` is floating, `total`` is integer. So, convert total
+               // to floating
+            {
+              total.type = t_floating;
+              total.floating = generic_math_op ((double)total.integer,
+                                                parsed.floating, op);
+            }
+        }
+      else // `total` is floating
+        {
+          if (parsed.type == t_integer)
+            total.floating
+                = generic_math_op (total.floating, (double)parsed.integer, op);
+          else // `parsed` is floating, `total`` is floating
+            total.floating
+                = generic_math_op (total.floating, parsed.floating, op);
+        }
+    }
+
+  return total;
+}
+
+static char *
+func_add (char *o, char **argv, const char *funcname)
+{
+  struct number n = perform_math_op (
+      op_add, funcname,
+      (struct math_operation_init){ .init_type = t_constant, .constant = 0 },
+      argv);
+
+  return number_to_string (o, n);
+}
+
+static char *
+func_subtract (char *o, char **argv, const char *funcname)
+{
+  struct math_operation_init init;
+  struct number n;
+
+  // If we received a single argument, negate it.
+  if (argv[0] != NULL && argv[1] == NULL)
+    {
+      init.init_type = t_constant;
+      init.constant = 0;
+    }
+  else
+    init.init_type = t_first_value;
+
+  n = perform_math_op (op_subtract, funcname, init, argv);
+
+  return number_to_string (o, n);
+}
+
+static char *
+func_multiply (char *o, char **argv, const char *funcname)
+{
+  struct number n = perform_math_op (
+      op_multiply, funcname,
+      (struct math_operation_init){ .init_type = t_constant, .constant = 1 },
+      argv);
+
+  return number_to_string (o, n);
+}
+
+static char *
+func_divide (char *o, char **argv, const char *funcname)
+{
+  struct number parsed;
+  struct number total;
+
+  // We either received one argument and we will return its inverse
or we will use it as
+  // our initial value for `total` and carry on dividing.
+  total = parse_number (*argv, funcname);
+
+  if (total.type == t_integer ? total.integer == 0 : total.floating == 0.0)
+    OS (fatal, *expanding_var,
+      _ ("Invalid argument to %s function: argument cannot be zero"),
funcname);
+
+  // If we received a single argument, compute its inverse.
+  if (argv[0] != NULL && argv[1] == NULL)
+    {
+      if (total.type == t_integer)
+        total.integer = 1 / total.integer;
+      else
+        total.floating = 1.0 / total.floating;
+
+      return number_to_string (o, total);
+    }
+
+  // We are using argv[0] as our initial value for total, so skip past it.
+  argv++;
+
+  for (; *argv != NULL; argv++)
+    {
+      parsed = parse_number (*argv, funcname);
+
+      if (parsed.type == t_integer ? parsed.integer == 0 :
parsed.floating == 0.0)
+        OS (fatal, *expanding_var,
+          _ ("Invalid argument to %s function: argument cannot be
zero"), funcname);
+      if (total.type == t_integer)
+        {
+          if (parsed.type == t_integer)
+            total.integer /= parsed.integer;
+          else // `parsed` is floating, `total`` is integer. So, convert total
+               // to floating
+            {
+              total.type = t_floating;
+              total.floating = (double)total.integer / parsed.floating;
+            }
+        }
+      else // `total` is floating
+        {
+          if (parsed.type == t_integer)
+            total.floating /= (double)parsed.integer;
+          else // `parsed` is floating, `total`` is floating
+            total.floating /= parsed.floating;
+        }
+    }
+
+  return number_to_string (o, total);
+}
+
+static char *
+func_modulus (char *o, char **argv, const char *funcname)
+{
+  struct number parsed;
+  struct number total;
+
+  // We require exactly two arguments
+  assert(argv[0] != NULL && argv[1] != NULL && argv[2] == NULL);
+
+  // We either received one argument and we will return its inverse
or we will use it as
+  // our initial value for `total` and carry on dividing.
+  total = parse_number (argv[0], funcname);
+
+  if (total.type == t_floating)
+    OS (fatal, *expanding_var,
+      _ ("Invalid argument to %s function: argument must be an
integer"), funcname);
+
+  if (total.integer == 0)
+    OS (fatal, *expanding_var,
+      _ ("Invalid argument to %s function: argument cannot be zero"),
funcname);
+
+  parsed = parse_number (argv[1], funcname);
+
+  if (parsed.type == t_floating)
+    OS (fatal, *expanding_var,
+      _ ("Invalid argument to %s function: argument must be an
integer"), funcname);
+
+  if (parsed.integer == 0)
+    OS (fatal, *expanding_var,
+      _ ("Invalid argument to %s function: argument cannot be zero"),
funcname);
+
+  total.integer %= parsed.integer;
+
+  return number_to_string (o, total);
+}
+
+static char *
+func_maximum (char *o, char **argv, const char *funcname)
+{
+  struct number n = perform_math_op (
+      op_max, funcname,
+      (struct math_operation_init){ .init_type = t_first_value},
+      argv);
+
+  return number_to_string (o, n);
+}
+
+static char *
+func_minimum (char *o, char **argv, const char *funcname)
+{
+  struct number n = perform_math_op (
+      op_min, funcname,
+      (struct math_operation_init){ .init_type = t_first_value},
+      argv);
+
+  return number_to_string (o, n);
+}
+
+static char *
+func_absolute_value (char *o, char **argv, const char *funcname)
+{
+  struct number n;
+
+  // We accept exactly one argumnent
+  assert(argv[0] != NULL && argv[1] == NULL);
+
+  n = parse_number (argv[0], funcname);
+
+  if (n.type == t_integer)
+    n.integer = llabs(n.integer);
+  else
+    n.floating = fabs(n.floating);
+
+  return number_to_string (o, n);
+}
+
 struct a_word
 {
   struct a_word *chain;
@@ -2394,6 +2785,8 @@ static char *func_call (char *o, char **argv,
const char *funcname);
 static const struct function_table_entry function_table_init[] =
 {
  /*         Name            MIN MAX EXP? Function */
+  FT_ENTRY ("abs",           1,  1,  1,  func_absolute_value),
+  FT_ENTRY ("add",           1,  0,  1,  func_add),
   FT_ENTRY ("abspath",       0,  1,  1,  func_abspath),
   FT_ENTRY ("addprefix",     2,  2,  1,  func_addsuffix_addprefix),
   FT_ENTRY ("addsuffix",     2,  2,  1,  func_addsuffix_addprefix),
@@ -2401,6 +2794,7 @@ static const struct function_table_entry
function_table_init[] =
   FT_ENTRY ("basename",      0,  1,  1,  func_basename_dir),
   FT_ENTRY ("call",          1,  0,  1,  func_call),
   FT_ENTRY ("dir",           0,  1,  1,  func_basename_dir),
+  FT_ENTRY ("div",           1,  0,  1,  func_divide),
   FT_ENTRY ("error",         0,  1,  1,  func_error),
   FT_ENTRY ("eval",          0,  1,  1,  func_eval),
   FT_ENTRY ("file",          1,  2,  1,  func_file),
@@ -2416,6 +2810,10 @@ static const struct function_table_entry
function_table_init[] =
   FT_ENTRY ("join",          2,  2,  1,  func_join),
   FT_ENTRY ("lastword",      0,  1,  1,  func_lastword),
   FT_ENTRY ("let",           3,  3,  0,  func_let),
+  FT_ENTRY ("max",           1,  0,  1,  func_maximum),
+  FT_ENTRY ("min",           1,  0,  1,  func_minimum),
+  FT_ENTRY ("mod",           2,  2,  1,  func_modulus),
+  FT_ENTRY ("mul",           1,  0,  1,  func_multiply),
   FT_ENTRY ("notdir",        0,  1,  1,  func_notdir_suffix),
   FT_ENTRY ("or",            1,  0,  0,  func_or),
   FT_ENTRY ("origin",        0,  1,  1,  func_origin),
@@ -2424,6 +2822,7 @@ static const struct function_table_entry
function_table_init[] =
   FT_ENTRY ("shell",         0,  1,  1,  func_shell),
   FT_ENTRY ("sort",          0,  1,  1,  func_sort),
   FT_ENTRY ("strip",         0,  1,  1,  func_strip),
+  FT_ENTRY ("sub",           1,  0,  1,  func_subtract),
   FT_ENTRY ("subst",         3,  3,  1,  func_subst),
   FT_ENTRY ("suffix",        0,  1,  1,  func_notdir_suffix),
   FT_ENTRY ("value",         0,  1,  1,  func_value),
-- 
2.43.0



reply via email to

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