[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
- [PATCH] Add arithmetic builtin functions,
Pete Dietl <=