tinymux/mux/modules/engine/funmath.cpp
Stephen Dennis 8808b4c375 feat(#2136): flip fargs to const UTF8 * const — and convert every site the compiler surfaced
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module
interfaces take `const UTF8 * const fargs[]`.  Double-const is
load-bearing: C++ qualification conversion needs const at both pointer
levels, so builder-side `UTF8 *[]` arrays convert implicitly — the
evaluator, the JIT marshaller, and every owner site need zero casts,
and slot reassignment inside bodies becomes a compile error for free.

The conversions: the flip landed first so the compiler enumerated every
violation; this commit is that inventory worked to zero — ~250 sites
across funceval, funceval2, functions, funmath, help, mail, session,
powers, levels, predicates, conf, walkdb, stringutil, timeutil/
date_scan (regenerated, one-line diff), exp3, and mux_main, each
classified per docs/campaign-2136-const-fargs.md's four recipes.

New idioms (functions.h): trim_space_sep_n() — non-destructive trim for
(pointer, length) consumers, so trim-then-scan sites need no copy at
all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers.
countwords() and DecodeListOfIntegers() rewritten non-destructive.

The flip deleted more than it added: #2157's fun_munge list1 copy, the
engine_com help-topic copy, fun_index's in-place NUL write, and five
const_casts (process_sex x4, sha1_helper).  const_cast budget: zero
added.

Trap recorded in the brief: an old-signature definition doesn't fail
the build — it becomes a C++ overload, and the new-signature symbol
stays undefined until dlopen(RTLD_NOW).  delim_check, the conn_bridge
bridges, the dbt_spike stub, and exp3::Call were all silently shadowed;
muxscript was the only host that noticed, because netmux's own net.cpp
resolved the flat-namespace lookup.  After any signature flip, grep the
old spelling.

Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and
make test-scenario, including the new tests/scenario/sidefx_fargs.py
that live-probes the class-3 wrappers smoke never touches (pemit/
trigger/link/tel/wipe/destroy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:34:23 -06:00

3636 lines
82 KiB
C++

/*! \file funmath.cpp
* \brief MUX math function handlers.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "sha1.h"
#include <algorithm>
#include <climits>
#include <memory>
#include <vector>
static const long nMaximums[10] =
{
0, 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999
};
static double g_aDoubles[MAX_WORDS];
FUNCTION(fun_add)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int nArgs = nfargs;
if (MAX_WORDS < nArgs)
{
nArgs = MAX_WORDS;
}
int i;
for (i = 0; i < nArgs; i++)
{
int nDigits;
long nMaxValue = 0;
if ( !is_integer(fargs[i], &nDigits)
|| nDigits > 9
|| (nMaxValue += nMaximums[nDigits]) > 999999999L)
{
// Do it the slow way.
//
for (int j = 0; j < nArgs; j++)
{
g_aDoubles[j] = mux_atof(fargs[j]);
}
fval(buff, bufc, AddDoubles(nArgs, g_aDoubles));
return;
}
}
// We can do it the fast way.
//
int64_t sum = 0;
for (i = 0; i < nArgs; i++)
{
sum += mux_atoi64(fargs[i]);
}
safe_i64toa(sum, buff, bufc);
}
FUNCTION(fun_ladd)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int n = 0;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_ladd.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while ( cp
&& n < MAX_WORDS)
{
UTF8 *curr = split_token(&cp, sep);
g_aDoubles[n++] = mux_atof(curr);
}
}
fval(buff, bufc, AddDoubles(n, g_aDoubles));
}
/////////////////////////////////////////////////////////////////
// Function : iadd(Arg[0], Arg[1],..,Arg[n])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////
FUNCTION(fun_iadd)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// #1861: signed overflow is UB; wrap via i64Add (see #1472 / timeutil.h).
//
int64_t sum = 0;
for (int i = 0; i < nfargs; i++)
{
sum = i64Add(sum, mux_atoi64(fargs[i]));
}
safe_i64toa(sum, buff, bufc);
}
FUNCTION(fun_sub)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
safe_i64toa(a - b, buff, bufc);
}
else
{
g_aDoubles[0] = mux_atof(fargs[0]);
g_aDoubles[1] = -mux_atof(fargs[1]);
fval(buff, bufc, AddDoubles(2, g_aDoubles));
}
}
/////////////////////////////////////////////////////////////////
// Function : isub(Arg[0], Arg[1])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////
FUNCTION(fun_isub)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// #1861: wrap via i64Sub rather than signed a - b.
//
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
safe_i64toa(i64Sub(a, b), buff, bufc);
}
FUNCTION(fun_mul)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double prod = 1.0;
for (int i = 0; i < nfargs; i++)
{
prod *= mux_atof(fargs[i]);
}
fval(buff, bufc, NearestPretty(prod));
}
/////////////////////////////////////////////////////////////////
// Function : imul(Arg[0], Arg[1], ... , Arg[n])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////
FUNCTION(fun_imul)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// #1861: wrap via i64Mul rather than signed *=.
//
int64_t prod = 1;
for (int i = 0; i < nfargs; i++)
{
prod = i64Mul(prod, mux_atoi64(fargs[i]));
}
safe_i64toa(prod, buff, bufc);
}
FUNCTION(fun_gt)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bResult = false;
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
bResult = (a > b);
}
else
{
double a = mux_atof(fargs[0]);
double b = mux_atof(fargs[1]);
bResult = (a > b);
}
safe_bool(bResult, buff, bufc);
}
FUNCTION(fun_gte)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bResult = false;
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
bResult = (a >= b);
}
else
{
double a = mux_atof(fargs[0]);
double b = mux_atof(fargs[1]);
bResult = (a >= b);
}
safe_bool(bResult, buff, bufc);
}
FUNCTION(fun_lt)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bResult = false;
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
bResult = (a < b);
}
else
{
double a = mux_atof(fargs[0]);
double b = mux_atof(fargs[1]);
bResult = (a < b);
}
safe_bool(bResult, buff, bufc);
}
FUNCTION(fun_lte)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bResult = false;
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
bResult = (a <= b);
}
else
{
double a = mux_atof(fargs[0]);
double b = mux_atof(fargs[1]);
bResult = (a <= b);
}
safe_bool(bResult, buff, bufc);
}
FUNCTION(fun_eq)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bResult = true;
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
bResult = (a == b);
}
else
{
if (strcmp(reinterpret_cast<const char *>(fargs[0]), reinterpret_cast<const char *>(fargs[1])) != 0)
{
double a = mux_atof(fargs[0]);
double b = mux_atof(fargs[1]);
bResult = (a == b);
}
}
safe_bool(bResult, buff, bufc);
}
FUNCTION(fun_neq)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bResult = false;
int nDigits;
if ( is_integer(fargs[0], &nDigits)
&& nDigits <= 9
&& is_integer(fargs[1], &nDigits)
&& nDigits <= 9)
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
bResult = (a != b);
}
else
{
if (strcmp(reinterpret_cast<const char *>(fargs[0]), reinterpret_cast<const char *>(fargs[1])) != 0)
{
double a = mux_atof(fargs[0]);
double b = mux_atof(fargs[1]);
bResult = (a != b);
}
}
safe_bool(bResult, buff, bufc);
}
/*
* ---------------------------------------------------------------------------
* * fun_max, fun_min: Return maximum (minimum) value.
*/
FUNCTION(fun_max)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double maximum = 0.0;
for (int i = 0; i < nfargs; i++)
{
double tval = mux_atof(fargs[i]);
if ( i == 0
|| tval > maximum)
{
maximum = tval;
}
}
fval(buff, bufc, maximum);
}
FUNCTION(fun_lmax)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double maximum = 0.0;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
int n = 0;
LBuf scList = LBuf_Src("fun_lmax.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (nullptr != cp)
{
UTF8 *curr = split_token(&cp, sep);
double tval = mux_atof(curr);
if ( n++ == 0
|| tval > maximum)
{
maximum = tval;
}
}
}
fval(buff, bufc, maximum);
}
FUNCTION(fun_min)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double minimum = 0.0;
for (int i = 0; i < nfargs; i++)
{
double tval = mux_atof(fargs[i]);
if ( i == 0
|| tval < minimum)
{
minimum = tval;
}
}
fval(buff, bufc, minimum);
}
FUNCTION(fun_lmin)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double minimum = 0.0;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
int n = 0;
LBuf scList = LBuf_Src("fun_lmin.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (nullptr != cp)
{
UTF8 *curr = split_token(&cp, sep);
double tval = mux_atof(curr);
if ( n++ == 0
|| tval < minimum)
{
minimum = tval;
}
}
}
fval(buff, bufc, minimum);
}
FUNCTION(fun_lmath)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// lmath(<operation>, <list>[, <delim>])
//
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
int n = 0;
LBuf scList = LBuf_Src("fun_lmath.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[1]), sep);
while ( cp
&& n < MAX_WORDS)
{
UTF8 *curr = split_token(&cp, sep);
g_aDoubles[n++] = mux_atof(curr);
}
if (n == 0)
{
safe_chr('0', buff, bufc);
return;
}
const UTF8 *op = fargs[0];
if ( mux_stricmp(op, T("add")) == 0
|| mux_stricmp(op, T("sum")) == 0)
{
fval(buff, bufc, AddDoubles(n, g_aDoubles));
}
else if (mux_stricmp(op, T("mul")) == 0)
{
double prod = 1.0;
for (int i = 0; i < n; i++)
{
prod *= g_aDoubles[i];
}
fval(buff, bufc, NearestPretty(prod));
}
else if (mux_stricmp(op, T("sub")) == 0)
{
double result = g_aDoubles[0];
for (int i = 1; i < n; i++)
{
result -= g_aDoubles[i];
}
fval(buff, bufc, NearestPretty(result));
}
else if (mux_stricmp(op, T("div")) == 0)
{
double result = g_aDoubles[0];
for (int i = 1; i < n; i++)
{
if (g_aDoubles[i] == 0.0)
{
safe_str(S_("#-1 DIVIDE BY ZERO"), buff, bufc);
return;
}
result /= g_aDoubles[i];
}
fval(buff, bufc, NearestPretty(result));
}
else if (mux_stricmp(op, T("mod")) == 0)
{
int64_t result = static_cast<int64_t>(g_aDoubles[0]);
for (int i = 1; i < n; i++)
{
int64_t divisor = static_cast<int64_t>(g_aDoubles[i]);
if (divisor == 0)
{
safe_str(S_("#-1 DIVIDE BY ZERO"), buff, bufc);
return;
}
result = i64Mod(result, divisor);
}
safe_i64toa(result, buff, bufc);
}
else if (mux_stricmp(op, T("min")) == 0)
{
double minimum = g_aDoubles[0];
for (int i = 1; i < n; i++)
{
if (g_aDoubles[i] < minimum)
{
minimum = g_aDoubles[i];
}
}
fval(buff, bufc, minimum);
}
else if (mux_stricmp(op, T("max")) == 0)
{
double maximum = g_aDoubles[0];
for (int i = 1; i < n; i++)
{
if (g_aDoubles[i] > maximum)
{
maximum = g_aDoubles[i];
}
}
fval(buff, bufc, maximum);
}
else if ( mux_stricmp(op, T("mean")) == 0
|| mux_stricmp(op, T("avg")) == 0)
{
fval(buff, bufc, AddDoubles(n, g_aDoubles) / n);
}
else if (mux_stricmp(op, T("median")) == 0)
{
// #1119: O(n log n) sort — insertion sort was O(n²) up to MAX_WORDS.
//
if (alarm_clock.alarmed)
{
safe_str(S_("#-1 CPU LIMITED"), buff, bufc);
return;
}
std::sort(g_aDoubles, g_aDoubles + n);
if (n % 2 == 1)
{
fval(buff, bufc, g_aDoubles[n / 2]);
}
else
{
g_aDoubles[0] = g_aDoubles[n / 2 - 1];
g_aDoubles[1] = g_aDoubles[n / 2];
fval(buff, bufc, AddDoubles(2, g_aDoubles) / 2.0);
}
}
else if (mux_stricmp(op, T("stddev")) == 0)
{
double mean = AddDoubles(n, g_aDoubles) / n;
double sumSqDiff = 0.0;
for (int i = 0; i < n; i++)
{
double diff = g_aDoubles[i] - mean;
sumSqDiff += diff * diff;
}
fval(buff, bufc, sqrt(sumSqDiff / n));
}
else
{
safe_str(S_("#-1 UNKNOWN OPERATION"), buff, bufc);
}
}
// ---------------------------------------------------------------------------
// limath: Integer-only list reduction, parallel to lmath().
//
// limath(<operation>, <list>[, <delim>])
//
// Supported operations: add/sum, sub, mul, div, mod, min, max, median.
// All arithmetic is 64-bit integer.
//
FUNCTION(fun_limath)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
// #1110: heap-allocate — vals[MAX_WORDS] was ~256 KiB on the stack and
// ate recursion margin under deep mux_exec (leaf, but still one big frame).
//
std::vector<int64_t> vals;
vals.reserve(64);
LBuf scList = LBuf_Src("fun_limath.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[1]), sep);
while (cp)
{
if (static_cast<int>(vals.size()) >= MAX_WORDS)
{
safe_str(S_("#-1 LIST TOO LONG"), buff, bufc);
return;
}
UTF8 *curr = split_token(&cp, sep);
if (!is_integer(curr, nullptr))
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
vals.push_back(mux_atoi64(curr));
}
const int n = static_cast<int>(vals.size());
if (n == 0)
{
safe_chr('0', buff, bufc);
return;
}
const UTF8 *op = fargs[0];
if ( mux_stricmp(op, T("add")) == 0
|| mux_stricmp(op, T("sum")) == 0)
{
// #1861: wrap via i64Add (see timeutil.h / #1472).
//
int64_t sum = 0;
for (int i = 0; i < n; i++)
{
sum = i64Add(sum, vals[i]);
}
safe_i64toa(sum, buff, bufc);
}
else if (mux_stricmp(op, T("mul")) == 0)
{
int64_t prod = 1;
for (int i = 0; i < n; i++)
{
prod = i64Mul(prod, vals[i]);
}
safe_i64toa(prod, buff, bufc);
}
else if (mux_stricmp(op, T("sub")) == 0)
{
int64_t result = vals[0];
for (int i = 1; i < n; i++)
{
result = i64Sub(result, vals[i]);
}
safe_i64toa(result, buff, bufc);
}
else if (mux_stricmp(op, T("div")) == 0)
{
int64_t result = vals[0];
for (int i = 1; i < n; i++)
{
if (vals[i] == 0)
{
safe_str(S_("#-1 DIVIDE BY ZERO"), buff, bufc);
return;
}
result = i64Division(result, vals[i]);
}
safe_i64toa(result, buff, bufc);
}
else if (mux_stricmp(op, T("mod")) == 0)
{
int64_t result = vals[0];
for (int i = 1; i < n; i++)
{
if (vals[i] == 0)
{
safe_str(S_("#-1 DIVIDE BY ZERO"), buff, bufc);
return;
}
result = i64Mod(result, vals[i]);
}
safe_i64toa(result, buff, bufc);
}
else if (mux_stricmp(op, T("min")) == 0)
{
int64_t minimum = vals[0];
for (int i = 1; i < n; i++)
{
if (vals[i] < minimum)
{
minimum = vals[i];
}
}
safe_i64toa(minimum, buff, bufc);
}
else if (mux_stricmp(op, T("max")) == 0)
{
int64_t maximum = vals[0];
for (int i = 1; i < n; i++)
{
if (vals[i] > maximum)
{
maximum = vals[i];
}
}
safe_i64toa(maximum, buff, bufc);
}
else if (mux_stricmp(op, T("median")) == 0)
{
// #1119: O(n log n) sort — insertion sort was O(n²) up to MAX_WORDS.
//
if (alarm_clock.alarmed)
{
safe_str(S_("#-1 CPU LIMITED"), buff, bufc);
return;
}
std::sort(vals.begin(), vals.end());
if (n % 2 == 1)
{
safe_i64toa(vals[n / 2], buff, bufc);
}
else
{
// Integer median of even-length list: floor of average.
// a + (b - a) / 2 with defined wrap (#1861) so INT64_MIN/MAX
// pairs cannot invoke signed overflow.
//
int64_t a = vals[n / 2 - 1];
int64_t b = vals[n / 2];
safe_i64toa(i64Add(a, i64Division(i64Sub(b, a), 2)), buff, bufc);
}
}
else
{
safe_str(S_("#-1 UNKNOWN OPERATION"), buff, bufc);
}
}
/* ---------------------------------------------------------------------------
* fun_sign: Returns -1, 0, or 1 based on the the sign of its argument.
*/
FUNCTION(fun_sign)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double num = mux_atof(fargs[0]);
if (num < 0)
{
safe_str(T("-1"), buff, bufc);
}
else
{
safe_bool(num > 0, buff, bufc);
}
}
// fun_isign: Returns -1, 0, or 1 based on the the sign of its argument.
//
FUNCTION(fun_isign)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int64_t num = mux_atoi64(fargs[0]);
if (num < 0)
{
safe_str(T("-1"), buff, bufc);
}
else
{
safe_bool(num > 0, buff, bufc);
}
}
// shl() and shr() borrowed from PennMUSH 1.50
//
FUNCTION(fun_shl)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( is_integer(fargs[0], nullptr)
&& is_integer(fargs[1], nullptr))
{
// #1109: shift count must be in [0, 63] for int64_t — larger is UB.
//
int64_t b = mux_atoi64(fargs[1]);
if (0 <= b && b < 64)
{
// #1472: bounding the count is necessary but not sufficient.
// Left-shifting a negative value is undefined however small the
// count, and so is shifting a bit into or past the sign bit --
// shl(-2,1), shl(1,63) and shl(3,63) all reach it. Shift in
// uint64_t, where the operation is defined as the modular one
// this function already documents, and convert back. Every
// result is bit-for-bit what the wrap produced before.
//
int64_t a = mux_atoi64(fargs[0]);
uint64_t ua = static_cast<uint64_t>(a) << b;
safe_i64toa(static_cast<int64_t>(ua), buff, bufc);
}
else if (b < 0)
{
// Keep historical wording for smoke tests (0 is allowed).
safe_str(S_("#-1 SECOND ARGUMENT MUST BE A POSITIVE NUMBER"), buff, bufc);
}
else
{
safe_str(S_("#-1 SECOND ARGUMENT MUST BE LESS THAN 64"), buff, bufc);
}
}
else
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
}
}
FUNCTION(fun_shr)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( is_integer(fargs[0], nullptr)
&& is_integer(fargs[1], nullptr))
{
// #1109: shift count must be in [0, 63] for int64_t — larger is UB.
//
int64_t b = mux_atoi64(fargs[1]);
if (0 <= b && b < 64)
{
int64_t a = mux_atoi64(fargs[0]);
safe_i64toa(a >> b, buff, bufc);
}
else if (b < 0)
{
safe_str(S_("#-1 SECOND ARGUMENT MUST BE A POSITIVE NUMBER"), buff, bufc);
}
else
{
safe_str(S_("#-1 SECOND ARGUMENT MUST BE LESS THAN 64"), buff, bufc);
}
}
else
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
}
}
FUNCTION(fun_inc)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if (nfargs == 1)
{
// #1472: wraps at INT64_MAX, which is signed overflow. Add in
// uint64_t for the same wrap without the undefined behaviour.
//
uint64_t v = static_cast<uint64_t>(mux_atoi64(fargs[0]));
safe_i64toa(static_cast<int64_t>(v + 1), buff, bufc);
}
else
{
safe_chr('1', buff, bufc);
}
}
FUNCTION(fun_dec)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if (nfargs == 1)
{
// #1472: wraps at INT64_MIN, which is signed overflow. Subtract in
// uint64_t for the same wrap without the undefined behaviour.
//
uint64_t v = static_cast<uint64_t>(mux_atoi64(fargs[0]));
safe_i64toa(static_cast<int64_t>(v - 1), buff, bufc);
}
else
{
safe_str(T("-1"), buff, bufc);
}
}
FUNCTION(fun_trunc)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double rArg = mux_atof(fargs[0]);
double rIntegerPart;
mux_FPRestore();
(void)modf(rArg, &rIntegerPart);
mux_FPSet();
#ifdef HAVE_IEEE_FP_FORMAT
int fpc = mux_fpclass(rIntegerPart);
if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
{
#endif // HAVE_IEEE_FP_FORMAT
fval(buff, bufc, rIntegerPart);
#ifdef HAVE_IEEE_FP_FORMAT
}
else
{
safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
}
#endif // HAVE_IEEE_FP_FORMAT
}
FUNCTION(fun_fdiv)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double bot = mux_atof(fargs[1]);
double top = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
if (bot == 0.0)
{
if (top > 0.0)
{
safe_str(T("+Inf"), buff, bufc);
}
else if (top < 0.0)
{
safe_str(T("-Inf"), buff, bufc);
}
else
{
safe_str(T("Ind"), buff, bufc);
}
}
else
{
fval(buff, bufc, top/bot);
}
#else
fval(buff, bufc, top/bot);
#endif
}
FUNCTION(fun_idiv)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int64_t bot, top;
bot = mux_atoi64(fargs[1]);
if (bot == 0)
{
safe_str(S_("#-1 DIVIDE BY ZERO"), buff, bufc);
}
else
{
top = mux_atoi64(fargs[0]);
top = i64Division(top, bot);
safe_i64toa(top, buff, bufc);
}
}
FUNCTION(fun_floordiv)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int64_t bot, top;
bot = mux_atoi64(fargs[1]);
if (bot == 0)
{
safe_str(S_("#-1 DIVIDE BY ZERO"), buff, bufc);
}
else
{
top = mux_atoi64(fargs[0]);
top = i64FloorDivision(top, bot);
safe_i64toa(top, buff, bufc);
}
}
FUNCTION(fun_mod)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int64_t bot, top;
bot = mux_atoi64(fargs[1]);
if (bot == 0)
{
bot = 1;
}
top = mux_atoi64(fargs[0]);
top = i64Mod(top, bot);
safe_i64toa(top, buff, bufc);
}
FUNCTION(fun_remainder)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int64_t bot, top;
bot = mux_atoi64(fargs[1]);
if (bot == 0)
{
bot = 1;
}
top = mux_atoi64(fargs[0]);
top = i64Remainder(top, bot);
safe_i64toa(top, buff, bufc);
}
/* ---------------------------------------------------------------------------
* fun_abs: Returns the absolute value of its argument.
*/
FUNCTION(fun_abs)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// #1255: |INT64_MIN| is 2**63. mux_atof can take the literal, but
// fval formats whole numbers near that magnitude as int64 and wraps
// back to a negative string — abs() returning a negative. Reject
// the exact integer domain like iabs() (#1114): error beats silent
// nonsense. Non-integer strings still take the float path.
//
int nDigits = 0;
if ( is_integer(fargs[0], &nDigits)
&& 0 < nDigits
&& mux_atoi64(fargs[0]) == INT64_MIN)
{
safe_range(buff, bufc);
return;
}
double num = mux_atof(fargs[0]);
if (0.0 == num)
{
safe_chr('0', buff, bufc);
}
else if (num < 0.0)
{
fval(buff, bufc, -num);
}
else
{
fval(buff, bufc, num);
}
}
// fun_iabs: Returns the absolute value of its argument.
//
FUNCTION(fun_iabs)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
int64_t num = mux_atoi64(fargs[0]);
if (num == 0)
{
safe_chr('0', buff, bufc);
}
else if (num == INT64_MIN)
{
// #1114: |INT64_MIN| is 2**63, which int64_t cannot represent —
// negating it is UB. Reject rather than hand back the magnitude
// as a string: "9223372036854775808" is not a valid int64, and
// every consumer corrupts it. Measured on this tree, feeding it
// to an integer-path function re-parses through mux_atoi64 (which
// wraps rather than saturates) straight back to INT64_MIN —
// idiv(iabs(-9223372036854775808),1) and shl(...,0) both yield
// -9223372036854775808 — while the float path loses precision
// instead (add(...,0) -> 9223372036854769664). Either way iabs()'s
// one invariant is silently broken. Failing loudly matches how the
// integer family handles an out-of-domain argument (fun_table,
// fun_columns).
//
safe_range(buff, bufc);
}
else if (num < 0)
{
safe_i64toa(-num, buff, bufc);
}
else
{
safe_i64toa(num, buff, bufc);
}
}
FUNCTION(fun_dist2d)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double a, b, d;
double sum;
a = mux_atof(fargs[0]);
b = mux_atof(fargs[2]);
d = a - b;
sum = d * d;
a = mux_atof(fargs[1]);
b = mux_atof(fargs[3]);
d = a - b;
sum += d * d;
mux_FPRestore();
double result = sqrt(sum);
mux_FPSet();
fval(buff, bufc, result);
}
FUNCTION(fun_dist3d)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double a, b, d;
double sum;
a = mux_atof(fargs[0]);
b = mux_atof(fargs[3]);
d = a - b;
sum = d * d;
a = mux_atof(fargs[1]);
b = mux_atof(fargs[4]);
d = a - b;
sum += d * d;
a = mux_atof(fargs[2]);
b = mux_atof(fargs[5]);
d = a - b;
sum += d * d;
mux_FPRestore();
double result = sqrt(sum);
mux_FPSet();
fval(buff, bufc, result);
}
//------------------------------------------------------------------------
// Vector functions: VADD, VSUB, VMUL, VCROSS, VMAG, VUNIT, VDIM
// Vectors are space-separated numbers.
//
#define VADD_F 0
#define VSUB_F 1
#define VMUL_F 2
#define VDOT_F 3
#define VCROSS_F 4
static void handle_vectors
(
const UTF8 *vecarg1, const UTF8 *vecarg2, UTF8 *buff, UTF8 **bufc,
const SEP &sep, const SEP &osep, int flag
)
{
// Return if the list is empty.
//
if (!vecarg1 || !*vecarg1 || !vecarg2 || !*vecarg2)
{
return;
}
// Uninitialized on purpose (#2145): list2arr writes arr[i] only for
// i < its return value and nothing reads past it, so value-initializing
// 2x 128 KB of pointers per call bought nothing — and was ~170x the
// cost of actually splitting a small vector.
//
std::unique_ptr<UTF8*[]> v1(new UTF8*[(LBUF_SIZE+1)/2]);
std::unique_ptr<UTF8*[]> v2(new UTF8*[(LBUF_SIZE+1)/2]);
// Split the lists up, or return if a list is empty. Non-destructive
// (#2136): vecarg1/vecarg2 are the caller's fargs, borrowed memory.
//
LBuf sc1 = LBuf_Src("handle_vectors.1");
LBuf sc2 = LBuf_Src("handle_vectors.2");
int n = list2arr_nd(v1.get(), (LBUF_SIZE+1)/2, vecarg1, sep, sc1);
int m = list2arr_nd(v2.get(), (LBUF_SIZE+1)/2, vecarg2, sep, sc2);
// vmul() and vadd() accepts a scalar in the first or second arg,
// but everything else has to be same-dimensional.
//
if ( n != m
&& !( ( flag == VMUL_F
|| flag == VADD_F
|| flag == VSUB_F)
&& ( n == 1
|| m == 1)))
{
safe_str(S_("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bufc);
return;
}
double scalar;
int i;
switch (flag)
{
case VADD_F:
// If n or m is 1, this is scalar addition.
// otherwise, add element-wise.
//
if (n == 1)
{
scalar = mux_atof(v1[0]);
for (i = 0; i < m; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
fval(buff, bufc, mux_atof(v2[i]) + scalar);
}
n = m;
}
else if (m == 1)
{
scalar = mux_atof(v2[0]);
for (i = 0; i < n; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
fval(buff, bufc, mux_atof(v1[i]) + scalar);
}
}
else
{
for (i = 0; i < n; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
double a = mux_atof(v1[i]);
double b = mux_atof(v2[i]);
fval(buff, bufc, a + b);
}
}
break;
case VSUB_F:
if (n == 1)
{
// This is a scalar minus a vector.
//
scalar = mux_atof(v1[0]);
for (i = 0; i < m; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
fval(buff, bufc, scalar - mux_atof(v2[i]));
}
}
else if (m == 1)
{
// This is a vector minus a scalar.
//
scalar = mux_atof(v2[0]);
for (i = 0; i < n; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
fval(buff, bufc, mux_atof(v1[i]) - scalar);
}
}
else
{
// This is a vector minus a vector.
//
for (i = 0; i < n; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
double a = mux_atof(v1[i]);
double b = mux_atof(v2[i]);
fval(buff, bufc, a - b);
}
}
break;
case VMUL_F:
// If n or m is 1, this is scalar multiplication.
// otherwise, multiply elementwise.
//
if (n == 1)
{
scalar = mux_atof(v1[0]);
for (i = 0; i < m; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
fval(buff, bufc, mux_atof(v2[i]) * scalar);
}
}
else if (m == 1)
{
scalar = mux_atof(v2[0]);
for (i = 0; i < n; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
fval(buff, bufc, mux_atof(v1[i]) * scalar);
}
}
else
{
// Vector element-wise product.
//
for (i = 0; i < n; i++)
{
if (i != 0)
{
print_sep(osep, buff, bufc);
}
double a = mux_atof(v1[i]);
double b = mux_atof(v2[i]);
fval(buff, bufc, a * b);
}
}
break;
case VDOT_F:
scalar = 0.0;
for (i = 0; i < n; i++)
{
double a = mux_atof(v1[i]);
double b = mux_atof(v2[i]);
scalar += a * b;
}
fval(buff, bufc, scalar);
break;
case VCROSS_F:
// cross product: (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
//
// Or in other words:
//
// | a b c |
// det | d e f | = i(bf-ce) + j(cd-af) + k(ae-bd)
// | i j k |
//
// where i, j, and k are unit vectors in the x, y, and z
// cartisian coordinate space and are understood when expressed
// in vector form.
//
if (n != 3)
{
safe_str(S_("#-1 VECTORS MUST BE DIMENSION OF 3"), buff, bufc);
}
else
{
double a[2][3];
for (i = 0; i < 3; i++)
{
a[0][i] = mux_atof(v1[i]);
a[1][i] = mux_atof(v2[i]);
}
fval(buff, bufc, (a[0][1] * a[1][2]) - (a[0][2] * a[1][1]));
print_sep(osep, buff, bufc);
fval(buff, bufc, (a[0][2] * a[1][0]) - (a[0][0] * a[1][2]));
print_sep(osep, buff, bufc);
fval(buff, bufc, (a[0][0] * a[1][1]) - (a[0][1] * a[1][0]));
}
break;
default:
// If we reached this, we're in trouble.
//
safe_str(S_("#-1 UNIMPLEMENTED"), buff, bufc);
}
}
FUNCTION(fun_vadd)
{
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
SEP osep = sep;
if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
{
return;
}
handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VADD_F);
}
FUNCTION(fun_vsub)
{
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
SEP osep = sep;
if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
{
return;
}
handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VSUB_F);
}
FUNCTION(fun_vmul)
{
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
SEP osep = sep;
if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
{
return;
}
handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VMUL_F);
}
FUNCTION(fun_vdot)
{
// dot product: (a,b,c) . (d,e,f) = ad + be + cf
//
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
SEP osep = sep;
if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
{
return;
}
handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VDOT_F);
}
FUNCTION(fun_vcross)
{
// cross product: (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
//
SEP sep;
if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
SEP osep = sep;
if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
{
return;
}
handle_vectors(fargs[0], fargs[1], buff, bufc, sep, osep, VCROSS_F);
}
FUNCTION(fun_vmag)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
// Split the list up, or return if the list is empty.
//
if (!fargs[0] || !*fargs[0])
{
return;
}
std::unique_ptr<UTF8*[]> v1(new UTF8*[LBUF_SIZE/2]); // uninit (#2145): past-count reads are UB now, not nullptr
LBuf sc = LBuf_Src("fun_vmag.nd");
int n = list2arr_nd(v1.get(), LBUF_SIZE/2, fargs[0], sep, sc);
// Calculate the magnitude.
//
double res = 0.0;
for (int i = 0; i < n; i++)
{
double tmp = mux_atof(v1[i]);
res += tmp * tmp;
}
if (res > 0)
{
mux_FPRestore();
double result = sqrt(res);
mux_FPSet();
fval(buff, bufc, result);
}
else
{
safe_chr('0', buff, bufc);
}
}
FUNCTION(fun_vunit)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
// Split the list up, or return if the list is empty.
//
if (!fargs[0] || !*fargs[0])
{
return;
}
std::unique_ptr<UTF8*[]> v1(new UTF8*[LBUF_SIZE/2]); // uninit (#2145): past-count reads are UB now, not nullptr
LBuf sc = LBuf_Src("fun_vunit.nd");
int n = list2arr_nd(v1.get(), LBUF_SIZE/2, fargs[0], sep, sc);
// Calculate the magnitude.
//
int i;
double res = 0.0;
for (i = 0; i < n; i++)
{
double tmp = mux_atof(v1[i]);
res += tmp * tmp;
}
if (res <= 0)
{
safe_str(S_("#-1 CANNOT MAKE UNIT VECTOR FROM ZERO-LENGTH VECTOR"),
buff, bufc);
return;
}
for (i = 0; i < n; i++)
{
if (0 != i)
{
print_sep(sep, buff, bufc);
}
mux_FPRestore();
double result = sqrt(res);
mux_FPSet();
fval(buff, bufc, mux_atof(v1[i]) / result);
}
}
FUNCTION(fun_floor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
mux_FPRestore();
double r = floor(mux_atof(fargs[0]));
mux_FPSet();
#ifdef HAVE_IEEE_FP_FORMAT
int fpc = mux_fpclass(r);
if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
{
#endif // HAVE_IEEE_FP_FORMAT
fval(buff, bufc, r);
#ifdef HAVE_IEEE_FP_FORMAT
}
else
{
safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
}
#endif // HAVE_IEEE_FP_FORMAT
}
FUNCTION(fun_ceil)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
mux_FPRestore();
double r = ceil(mux_atof(fargs[0]));
mux_FPSet();
#ifdef HAVE_IEEE_FP_FORMAT
int fpc = mux_fpclass(r);
if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
{
#endif // HAVE_IEEE_FP_FORMAT
fval(buff, bufc, r);
#ifdef HAVE_IEEE_FP_FORMAT
}
else
{
safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
}
#endif // HAVE_IEEE_FP_FORMAT
}
FUNCTION(fun_round)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double r = mux_atof(fargs[0]);
#ifdef HAVE_IEEE_FP_FORMAT
int fpc = mux_fpclass(r);
if ( MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS
|| MUX_FPGROUP(fpc) == MUX_FPGROUP_ZERO)
{
if (MUX_FPGROUP(fpc) == MUX_FPGROUP_ZERO)
{
r = 0.0;
}
#endif // HAVE_IEEE_FP_FORMAT
int64_t frac = mux_atoi64(fargs[1]);
safe_str(mux_ftoa(r, true, frac), buff, bufc);
#ifdef HAVE_IEEE_FP_FORMAT
}
else
{
safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
}
#endif // HAVE_IEEE_FP_FORMAT
}
FUNCTION(fun_pi)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(fargs);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
safe_str(T("3.141592653589793"), buff, bufc);
}
FUNCTION(fun_e)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(fargs);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
safe_str(T("2.718281828459045"), buff, bufc);
}
static double ConvertRDG2R(double d, const UTF8 *szUnits)
{
switch (szUnits[0])
{
case 'd':
case 'D':
// Degrees to Radians.
//
d *= 0.017453292519943295;
break;
case 'g':
case 'G':
// Gradians to Radians.
//
d *= 0.015707963267948967;
break;
}
return d;
}
static double ConvertR2RDG(double d, const UTF8 *szUnits)
{
switch (szUnits[0])
{
case 'd':
case 'D':
// Radians to Degrees.
//
d *= 57.29577951308232;
break;
case 'g':
case 'G':
// Radians to Gradians.
//
d *= 63.66197723675813;
break;
}
return d;
}
FUNCTION(fun_ctu)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val = mux_atof(fargs[0]);
val = ConvertRDG2R(val, fargs[1]);
val = ConvertR2RDG(val, fargs[2]);
fval(buff, bufc, val);
}
FUNCTION(fun_sin)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double d = mux_atof(fargs[0]);
if (nfargs == 2)
{
d = ConvertRDG2R(d, fargs[1]);
}
mux_FPRestore();
d = sin(d);
mux_FPSet();
fval(buff, bufc, d);
}
FUNCTION(fun_cos)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double d = mux_atof(fargs[0]);
if (nfargs == 2)
{
d = ConvertRDG2R(d, fargs[1]);
}
mux_FPRestore();
d = cos(d);
mux_FPSet();
fval(buff, bufc, d);
}
FUNCTION(fun_tan)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double d = mux_atof(fargs[0]);
if (nfargs == 2)
{
d = ConvertRDG2R(d, fargs[1]);
}
mux_FPRestore();
d = tan(d);
mux_FPSet();
fval(buff, bufc, d);
}
FUNCTION(fun_asin)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
if ((val < -1.0) || (val > 1.0))
{
safe_str(T("Ind"), buff, bufc);
return;
}
#endif
mux_FPRestore();
val = asin(val);
mux_FPSet();
if (nfargs == 2)
{
val = ConvertR2RDG(val, fargs[1]);
}
fval(buff, bufc, val);
}
FUNCTION(fun_acos)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
if ((val < -1.0) || (val > 1.0))
{
safe_str(T("Ind"), buff, bufc);
return;
}
#endif
mux_FPRestore();
val = acos(val);
mux_FPSet();
if (nfargs == 2)
{
val = ConvertR2RDG(val, fargs[1]);
}
fval(buff, bufc, val);
}
FUNCTION(fun_atan)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val = mux_atof(fargs[0]);
mux_FPRestore();
val = atan(val);
mux_FPSet();
if (nfargs == 2)
{
val = ConvertR2RDG(val, fargs[1]);
}
fval(buff, bufc, val);
}
FUNCTION(fun_atan2)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val1 = mux_atof(fargs[0]);
double val2 = mux_atof(fargs[1]);
mux_FPRestore();
val1 = atan2(val1, val2);
mux_FPSet();
if (3 == nfargs)
{
val1 = ConvertR2RDG(val1, fargs[2]);
}
fval(buff, bufc, val1);
}
FUNCTION(fun_exp)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val = mux_atof(fargs[0]);
mux_FPRestore();
val = exp(val);
mux_FPSet();
fval(buff, bufc, val);
}
FUNCTION(fun_power)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val, val1, val2;
val1 = mux_atof(fargs[0]);
val2 = mux_atof(fargs[1]);
#ifndef HAVE_IEEE_FP_SNAN
if (val1 < 0.0)
{
safe_str(T("Ind"), buff, bufc);
}
else
{
mux_FPRestore();
val = pow(val1, val2);
mux_FPSet();
fval(buff, bufc, val);
}
#else
mux_FPRestore();
val = pow(val1, val2);
mux_FPSet();
fval(buff, bufc, val);
#endif
}
FUNCTION(fun_fmod)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val, val1, val2;
val1 = mux_atof(fargs[0]);
val2 = mux_atof(fargs[1]);
#ifndef HAVE_IEEE_FP_SNAN
// The indeterminate case is a zero *divisor* (fmod(x,0) is NaN);
// fmod(0,y) is a valid 0. The guard checked val1 (the dividend),
// copy-pasted from fun_power's val1 sign check — so on a
// NO_IEEE_FP_SNAN lane fmod(0,3) wrongly returned Ind and fmod(x,0)
// fell through unguarded. Reported by ThresholdOps.
if (val2 == 0.0)
{
safe_str(T("Ind"), buff, bufc);
}
else
{
mux_FPRestore();
val = fmod(val1, val2);
mux_FPSet();
fval(buff, bufc, val);
}
#else
mux_FPRestore();
val = fmod(val1, val2);
mux_FPSet();
fval(buff, bufc, val);
#endif
}
FUNCTION(fun_ln)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val;
val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
if (val < 0.0)
{
safe_str(T("Ind"), buff, bufc);
}
else if (val == 0.0)
{
safe_str(T("-Inf"), buff, bufc);
}
else
{
mux_FPRestore();
val = log(val);
mux_FPSet();
}
#else
mux_FPRestore();
val = log(val);
mux_FPSet();
#endif
fval(buff, bufc, val);
}
FUNCTION(fun_log)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
typedef enum
{
#ifdef HAVE_LOG2
kBinary,
#endif
kNatural,
kCommon,
kOther
} logarithm_base;
logarithm_base kBase;
double val = mux_atof(fargs[0]);
double base;
if (2 == nfargs)
{
int nDigits;
if ( is_integer(fargs[1], &nDigits)
&& nDigits <= 2)
{
int64_t iBase = mux_atoi64(fargs[1]);
if (10 == iBase)
{
kBase = kCommon;
}
#ifdef HAVE_LOG2
else if (2 == iBase)
{
kBase = kBinary;
}
#endif
else
{
kBase = kOther;
base = mux_atof(fargs[1]);
}
}
else if ( 'e' == fargs[1][0]
&& '\0' == fargs[1][1])
{
kBase = kNatural;
}
else
{
kBase = kOther;
base = mux_atof(fargs[1]);
}
}
else
{
kBase = kCommon;
}
if ( kOther == kBase
&& base <= 1)
{
safe_str(S_("#-1 BASE OUT OF RANGE"), buff, bufc);
return;
}
#ifndef HAVE_IEEE_FP_SNAN
if (val < 0.0)
{
safe_str(T("Ind"), buff, bufc);
return;
}
else if (0.0 == val)
{
safe_str(T("-Inf"), buff, bufc);
return;
}
else
#endif
{
mux_FPRestore();
if (kCommon == kBase)
{
val = log10(val);
}
else if (kNatural == kBase)
{
val = log(val);
}
#ifdef HAVE_LOG2
else if (kBinary == kBase)
{
val = log2(val);
}
#endif
else
{
val = log(val)/log(base);
}
mux_FPSet();
}
fval(buff, bufc, val);
}
FUNCTION(fun_sqrt)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
double val;
val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
if (val < 0.0)
{
safe_str(T("Ind"), buff, bufc);
}
else if (val == 0.0)
{
safe_chr('0', buff, bufc);
}
else
{
mux_FPRestore();
val = sqrt(val);
mux_FPSet();
}
#else
mux_FPRestore();
val = sqrt(val);
mux_FPSet();
#endif
fval(buff, bufc, val);
}
/* ---------------------------------------------------------------------------
* isnum: is the argument a number?
*/
FUNCTION(fun_isnum)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
safe_bool(is_real(fargs[0]), buff, bufc);
}
/* ---------------------------------------------------------------------------
* israt: is the argument an rational?
*/
FUNCTION(fun_israt)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
safe_bool(is_rational(fargs[0]), buff, bufc);
}
/* ---------------------------------------------------------------------------
* isint: is the argument an integer?
*/
FUNCTION(fun_isint)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
safe_bool(is_integer(fargs[0], nullptr), buff, bufc);
}
FUNCTION(fun_and)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool val = true;
for (int i = 0; i < nfargs && val; i++)
{
val = isTRUE(mux_atoi64(fargs[i]));
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_or)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool val = false;
for (int i = 0; i < nfargs && !val; i++)
{
val = isTRUE(mux_atoi64(fargs[i]));
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_andbool)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool val = true;
for (int i = 0; i < nfargs && val; i++)
{
val = xlate(fargs[i]);
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_orbool)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool val = false;
for (int i = 0; i < nfargs && !val; i++)
{
val = xlate(fargs[i]);
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_cand)
{
bool val = true;
LBuf temp = LBuf_Src("fun_cand");
for (int i = 0; i < nfargs && val && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
val = isTRUE(mux_atoi64(temp));
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_cor)
{
bool val = false;
LBuf temp = LBuf_Src("fun_cor");
for (int i = 0; i < nfargs && !val && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
val = isTRUE(mux_atoi64(temp));
}
safe_bool(val, buff, bufc);
}
// firstof(arg1, arg2, ...) — return the first true (non-zero) argument.
// Evaluates left-to-right, stops at first true. If none are true,
// returns the last evaluated value.
//
FUNCTION(fun_firstof)
{
if (0 == nfargs)
{
return;
}
LBuf temp = LBuf_Src("fun_firstof");
for (int i = 0; i < nfargs && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
if (isTRUE(mux_atoi64(temp)) || i == nfargs - 1)
{
safe_str(temp, buff, bufc);
return;
}
}
}
// strfirstof(arg1, arg2, ...) — return the first non-empty string argument.
// Evaluates left-to-right, stops at first non-empty. If all are empty,
// returns empty string.
//
FUNCTION(fun_strfirstof)
{
if (0 == nfargs)
{
return;
}
LBuf temp = LBuf_Src("fun_strfirstof");
for (int i = 0; i < nfargs && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
if ('\0' != temp[0] || i == nfargs - 1)
{
safe_str(temp, buff, bufc);
return;
}
}
}
// allof(arg1, arg2, ...[, osep]) — return all true (non-zero) arguments,
// separated by osep. Last arg is the output separator if nfargs >= 2.
//
FUNCTION(fun_allof)
{
if (0 == nfargs)
{
return;
}
// Last argument is the output separator.
//
int nArgs = nfargs;
LBuf osep = LBuf_Src("fun_allof");
osep[0] = ' ';
osep[1] = '\0';
size_t nOsep = 1;
if (nfargs >= 2)
{
UTF8 *sp = osep;
mux_exec(fargs[nfargs - 1], LBUF_SIZE-1, osep, &sp, executor, caller,
enactor, eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*sp = '\0';
nOsep = sp - osep;
nArgs = nfargs - 1;
}
bool bFirst = true;
LBuf temp = LBuf_Src("fun_allof");
for (int i = 0; i < nArgs && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
if (isTRUE(mux_atoi64(temp)))
{
if (!bFirst)
{
safe_copy_buf(osep, nOsep, buff, bufc);
}
safe_str(temp, buff, bufc);
bFirst = false;
}
}
}
// strallof(arg1, arg2, ...[, osep]) — return all non-empty string arguments,
// separated by osep. Last arg is the output separator if nfargs >= 2.
//
FUNCTION(fun_strallof)
{
if (0 == nfargs)
{
return;
}
int nArgs = nfargs;
LBuf osep = LBuf_Src("fun_strallof");
osep[0] = ' ';
osep[1] = '\0';
size_t nOsep = 1;
if (nfargs >= 2)
{
UTF8 *sp = osep;
mux_exec(fargs[nfargs - 1], LBUF_SIZE-1, osep, &sp, executor, caller,
enactor, eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*sp = '\0';
nOsep = sp - osep;
nArgs = nfargs - 1;
}
bool bFirst = true;
LBuf temp = LBuf_Src("fun_strallof");
for (int i = 0; i < nArgs && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
if ('\0' != temp[0])
{
if (!bFirst)
{
safe_copy_buf(osep, nOsep, buff, bufc);
}
safe_str(temp, buff, bufc);
bFirst = false;
}
}
}
FUNCTION(fun_candbool)
{
bool val = true;
LBuf temp = LBuf_Src("fun_candbool");
for (int i = 0; i < nfargs && val && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
val = xlate(temp);
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_corbool)
{
bool val = false;
LBuf temp = LBuf_Src("fun_corbool");
for (int i = 0; i < nfargs && !val && !alarm_clock.alarmed; i++)
{
UTF8 *bp = temp.get();
mux_exec(fargs[i], LBUF_SIZE-1, temp, &bp, executor, caller, enactor,
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, cargs, ncargs);
*bp = '\0';
val = xlate(temp);
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_xor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool val = false;
for (int i = 0; i < nfargs; i++)
{
// Test truthiness on the full 64-bit value, matching and()/or()/
// lxor(); narrowing to int drops the high bits of large operands.
//
bool tval = isTRUE(mux_atoi64(fargs[i]));
val = (val && !tval) || (!val && tval);
}
safe_bool(val, buff, bufc);
}
FUNCTION(fun_not)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
safe_bool(!xlate(fargs[0]), buff, bufc);
}
FUNCTION(fun_t)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( nfargs <= 0
|| fargs[0][0] == '\0')
{
safe_chr('0', buff, bufc);
}
else
{
safe_bool(xlate(fargs[0]), buff, bufc);
}
}
static const UTF8 *bigones[] =
{
T(""),
T("thousand"),
T("million"),
T("billion"),
T("trillion")
};
static const UTF8 *singles[] =
{
T(""),
T("one"),
T("two"),
T("three"),
T("four"),
T("five"),
T("six"),
T("seven"),
T("eight"),
T("nine")
};
static const UTF8 *teens[] =
{
T("ten"),
T("eleven"),
T("twelve"),
T("thirteen"),
T("fourteen"),
T("fifteen"),
T("sixteen"),
T("seventeen"),
T("eighteen"),
T("nineteen")
};
static const UTF8 *tens[] =
{
T(""),
T(""),
T("twenty"),
T("thirty"),
T("forty"),
T("fifty"),
T("sixty"),
T("seventy"),
T("eighty"),
T("ninety")
};
static const UTF8 *th_prefix[] =
{
T(""),
T("ten"),
T("hundred")
};
class CSpellNum
{
public:
void SpellNum(const UTF8 *p, UTF8 *buff_arg, UTF8 **bufc_arg);
private:
void TwoDigits(const UTF8 *p);
void ThreeDigits(const UTF8 *p, size_t iBigOne);
void ManyDigits(size_t n, const UTF8 *p, bool bHundreds);
void FractionalDigits(size_t n, const UTF8 *p);
void StartWord(void);
void AddWord(const UTF8 *p);
UTF8 *buff;
UTF8 **bufc;
bool bNeedSpace;
};
void CSpellNum::StartWord(void)
{
if (bNeedSpace)
{
safe_chr(' ', buff, bufc);
}
bNeedSpace = true;
}
void CSpellNum::AddWord(const UTF8 *p)
{
safe_str(p, buff, bufc);
}
// Handle two-character sequences.
//
void CSpellNum::TwoDigits(const UTF8 *p)
{
int n0 = p[0] - '0';
int n1 = p[1] - '0';
if (n0 == 0)
{
if (n1 != 0)
{
StartWord();
AddWord(singles[n1]);
}
return;
}
else if (n0 == 1)
{
StartWord();
AddWord(teens[n1]);
return;
}
if (n1 == 0)
{
StartWord();
AddWord(tens[n0]);
}
else
{
StartWord();
AddWord(tens[n0]);
AddWord(T("-"));
AddWord(singles[n1]);
}
}
// Handle three-character sequences.
//
void CSpellNum::ThreeDigits(const UTF8 *p, size_t iBigOne)
{
if ( p[0] == '0'
&& p[1] == '0'
&& p[2] == '0')
{
return;
}
// Handle hundreds.
//
if (p[0] != '0')
{
StartWord();
AddWord(singles[p[0]-'0']);
StartWord();
AddWord(T("hundred"));
}
TwoDigits(p+1);
if (iBigOne > 0)
{
StartWord();
AddWord(bigones[iBigOne]);
}
}
// Handle a series of patterns of three.
//
void CSpellNum::ManyDigits(size_t n, const UTF8 *p, bool bHundreds)
{
// Handle special Hundreds cases.
//
if ( bHundreds
&& n == 4
&& p[1] != '0')
{
TwoDigits(p);
StartWord();
AddWord(T("hundred"));
TwoDigits(p+2);
return;
}
// Handle normal cases.
//
size_t ndiv = ((n + 2) / 3) - 1;
size_t nrem = n % 3;
UTF8 buf[3];
if (nrem == 0)
{
nrem = 3;
}
size_t j = nrem;
for (int i = 2; 0 <= i; i--)
{
if (j)
{
j--;
buf[i] = p[j];
}
else
{
buf[i] = '0';
}
}
ThreeDigits(buf, ndiv);
p += nrem;
while (ndiv-- > 0)
{
ThreeDigits(p, ndiv);
p += 3;
}
}
// Handle precision ending for part to the right of the decimal place.
//
void CSpellNum::FractionalDigits(size_t n, const UTF8 *p)
{
ManyDigits(n, p, false);
if ( 0 < n
&& n < 15)
{
size_t d = n / 3;
size_t r = n % 3;
StartWord();
if (r != 0)
{
AddWord(th_prefix[r]);
if (d != 0)
{
AddWord(T("-"));
}
}
AddWord(bigones[d]);
AddWord(T("th"));
int64_t i64 = mux_atoi64(p);
if (i64 != 1)
{
AddWord(T("s"));
}
}
}
void CSpellNum::SpellNum(const UTF8 *number, UTF8 *buff_arg, UTF8 **bufc_arg)
{
buff = buff_arg;
bufc = bufc_arg;
bNeedSpace = false;
// Trim Spaces from beginning.
//
while (mux_isspace(*number))
{
number++;
}
if (*number == '-')
{
StartWord();
AddWord(T("negative"));
number++;
}
// Trim Zeroes from Beginning.
//
while (*number == '0')
{
number++;
}
const UTF8 *pA = number;
while (mux_isdigit(*number))
{
number++;
}
size_t nA = number - pA;
const UTF8 *pB = nullptr;
size_t nB = 0;
if (*number == '.')
{
number++;
pB = number;
while (mux_isdigit(*number))
{
number++;
}
nB = number - pB;
}
// Skip trailing spaces.
//
while (mux_isspace(*number))
{
number++;
}
if ( *number
|| nA >= 16
|| nB >= 15)
{
safe_str(S_("#-1 ARGUMENT MUST BE A NUMBER"), buff, bufc);
return;
}
if (nA == 0)
{
if (nB == 0)
{
StartWord();
AddWord(T("zero"));
}
}
else
{
ManyDigits(nA, pA, true);
if (nB)
{
StartWord();
AddWord(T("and"));
}
}
if (nB)
{
FractionalDigits(nB, pB);
}
}
FUNCTION(fun_spellnum)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
CSpellNum sn;
sn.SpellNum(fargs[0], buff, bufc);
}
FUNCTION(fun_roman)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
const UTF8 *number = fargs[0];
// Trim Spaces from beginning.
//
while (mux_isspace(*number))
{
number++;
}
// Trim Zeroes from Beginning.
//
while (*number == '0')
{
number++;
}
const UTF8 *pA = number;
while (mux_isdigit(*number))
{
number++;
}
size_t nA = number - pA;
// Skip trailing spaces.
//
while (mux_isspace(*number))
{
number++;
}
// Validate that argument is numeric with a value between 1 and 3999.
//
if (*number || nA < 1)
{
safe_str(S_("#-1 ARGUMENT MUST BE A POSITIVE NUMBER"), buff, bufc);
return;
}
else if ( nA > 4
|| ( nA == 1
&& pA[0] == '0')
|| ( nA == 4
&& '3' < pA[0]))
{
safe_range(buff, bufc);
return;
}
// I:1, V:5, X:10, L:50, C:100, D:500, M:1000
//
// Ones: _ I II III IV V VI VII VIII IX
// Tens: _ X XX XXX XL L LX LXX LXXX XC
// Hundreds: _ C CC CCC CD D DC DCC DCCC CM
// Thousands: _ M MM MMM
//
static const UTF8 aLetters[4][3] =
{
{ 'I', 'V', 'X' },
{ 'X', 'L', 'C' },
{ 'C', 'D', 'M' },
{ 'M', ' ', ' ' }
};
static const UTF8 *aCode[10] =
{
T(""),
T("1"),
T("11"),
T("111"),
T("12"),
T("2"),
T("21"),
T("211"),
T("2111"),
T("13")
};
while (nA--)
{
const UTF8 *pCode = aCode[*pA - '0'];
const UTF8 *pLetters = aLetters[nA];
while (*pCode)
{
safe_chr(pLetters[*pCode - '1'], buff, bufc);
pCode++;
}
pA++;
}
}
/*-------------------------------------------------------------------------
* List-based numeric functions.
*/
FUNCTION(fun_land)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bValue = true;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_lbool.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (cp && bValue)
{
UTF8 *curr = split_token(&cp, sep);
bValue = isTRUE(mux_atoi64(curr));
}
}
safe_bool(bValue, buff, bufc);
}
FUNCTION(fun_lor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bValue = false;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_lbool.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (cp && !bValue)
{
UTF8 *curr = split_token(&cp, sep);
bValue = isTRUE(mux_atoi64(curr));
}
}
safe_bool(bValue, buff, bufc);
}
FUNCTION(fun_lxor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bValue = false;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_lbool.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (cp)
{
UTF8 *curr = split_token(&cp, sep);
bool bCurr = isTRUE(mux_atoi64(curr));
bValue = (bValue && !bCurr) || (!bValue && bCurr);
}
}
safe_bool(bValue, buff, bufc);
}
FUNCTION(fun_lband)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint64_t val = UINT64_MAX;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_lbool.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (cp)
{
UTF8 *curr = split_token(&cp, sep);
if (!is_integer(curr, nullptr))
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
val &= mux_atoi64(curr);
}
}
safe_i64toa(val, buff, bufc);
}
FUNCTION(fun_lbor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint64_t val = 0;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_lbool.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (cp)
{
UTF8 *curr = split_token(&cp, sep);
if (!is_integer(curr, nullptr))
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
val |= mux_atoi64(curr);
}
}
safe_i64toa(val, buff, bufc);
}
FUNCTION(fun_lbxor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint64_t val = 0;
if (0 < nfargs)
{
SEP sep;
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
{
return;
}
LBuf scList = LBuf_Src("fun_lbool.list");
UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep);
while (cp)
{
UTF8 *curr = split_token(&cp, sep);
if (!is_integer(curr, nullptr))
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
val ^= mux_atoi64(curr);
}
}
safe_i64toa(val, buff, bufc);
}
FUNCTION(fun_band)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint64_t val = UINT64_MAX;
for (int i = 0; i < nfargs; i++)
{
if (is_integer(fargs[i], nullptr))
{
val &= mux_atoi64(fargs[i]);
}
else
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
}
safe_i64toa(val, buff, bufc);
}
FUNCTION(fun_bor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint64_t val = 0;
for (int i = 0; i < nfargs; i++)
{
if (is_integer(fargs[i], nullptr))
{
val |= mux_atoi64(fargs[i]);
}
else
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
}
safe_i64toa(val, buff, bufc);
}
FUNCTION(fun_bnand)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nfargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( is_integer(fargs[0], nullptr)
&& is_integer(fargs[1], nullptr))
{
int64_t a = mux_atoi64(fargs[0]);
int64_t b = mux_atoi64(fargs[1]);
safe_i64toa(a & ~(b), buff, bufc);
}
else
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
}
}
FUNCTION(fun_bxor)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint64_t val = 0;
for (int i = 0; i < nfargs; i++)
{
if (is_integer(fargs[i], nullptr))
{
val ^= mux_atoi64(fargs[i]);
}
else
{
safe_str(S_("#-1 ARGUMENTS MUST BE INTEGERS"), buff, bufc);
return;
}
}
safe_i64toa(val, buff, bufc);
}
FUNCTION(fun_crc32)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
uint32_t ulCRC32 = 0;
for (int i = 0; i < nfargs; i++)
{
size_t n = strlen(reinterpret_cast<const char *>(fargs[i]));
ulCRC32 = CRC32_ProcessBuffer(ulCRC32, fargs[i], n);
}
safe_i64toa(ulCRC32, buff, bufc);
}
void safe_hex(uint8_t md[], size_t len, bool bUpper, UTF8 *buff, UTF8 **bufc)
{
std::vector<UTF8> buf((len * 2) + 1);
int bufoffset = 0;
const UTF8 *Digits16 = bUpper ? Digits16U : Digits16L;
for (size_t i = 0; i < len; i++)
{
uint8_t c = md[i];
buf[bufoffset++] = Digits16[(c >> 4) & 0x0F];
buf[bufoffset++] = Digits16[(c ) & 0x0F];
}
buf[bufoffset] = '\0';
safe_str(buf.data(), buff, bufc);
}
void sha1_helper(int nfargs, const UTF8 * const fargs[], UTF8 *buff, UTF8 **bufc)
{
#ifdef UNIX_DIGEST
uint8_t md[EVP_MAX_MD_SIZE];
#else
uint8_t md[MUX_SHA1_DIGEST_LENGTH];
#endif
unsigned int len = 0;
std::vector<size_t> lens(nfargs);
for (int i = 0; i < nfargs; i++)
{
lens[i] = strlen(reinterpret_cast<const char *>(fargs[i]));
}
if (!mux_sha1_digest(fargs, lens.data(), nfargs, md, &len))
{
safe_str(S_("#-1 UNSUPPORTED"), buff, bufc);
return;
}
safe_hex(md, len, true, buff, bufc);
}
FUNCTION(fun_sha1)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
sha1_helper(nfargs, fargs, buff, bufc);
}
FUNCTION(fun_digest)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
#ifdef UNIX_DIGEST
EVP_MD_CTX *ctx;
#if HAVE_EVP_MD_CTX_NEW
ctx = EVP_MD_CTX_new();
#elif HAVE_EVP_MD_CTX_CREATE
ctx = EVP_MD_CTX_create();
#else
#error Need EVP_MD_CTX_new() or EVP_MD_CTX_create().
#endif
// Resolve the (user-supplied) digest name. On OpenSSL 3.0+ use the
// provider-native EVP_MD_fetch(): unlike the legacy EVP_get_digestbyname(),
// it resolves algorithm aliases such as "sha-1" deterministically from a
// cold process, with no dependence on an earlier digest call having lazily
// loaded the default provider (#1961). A fetched EVP_MD is a reference that
// must be freed; the legacy pointer is a static const that must not be.
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
EVP_MD *mp = EVP_MD_fetch(nullptr, reinterpret_cast<const char *>(fargs[0]), nullptr);
#else
const EVP_MD *mp = EVP_get_digestbyname(reinterpret_cast<const char *>(fargs[0]));
#endif
if (nullptr == mp)
{
// Release the context allocated above before bailing (#1961 leak fix).
#if HAVE_EVP_MD_CTX_NEW
EVP_MD_CTX_free(ctx);
#elif HAVE_EVP_MD_CTX_CREATE
EVP_MD_CTX_destroy(ctx);
#endif
safe_str(S_("#-1 UNSUPPORTED DIGEST TYPE"), buff, bufc);
return;
}
EVP_DigestInit(ctx, mp);
int i;
for (i = 1; i < nfargs; i++)
{
EVP_DigestUpdate(ctx, fargs[i], strlen(reinterpret_cast<const char *>(fargs[i])));
}
unsigned int len = 0;
uint8_t md[EVP_MAX_MD_SIZE];
EVP_DigestFinal(ctx, md, &len);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
EVP_MD_free(mp);
#endif
#if HAVE_EVP_MD_CTX_NEW
EVP_MD_CTX_free(ctx);
#elif HAVE_EVP_MD_CTX_CREATE
EVP_MD_CTX_destroy(ctx);
#else
#error Need EVP_MD_CTX_new() or EVP_MD_CTX_create().
#endif
safe_hex(md, len, true, buff, bufc);
#else
// CNG backend (#1963): sha1/sha256/sha384/sha512/md5, resolved by
// mux_digest. Same #-1 on unknown names as the OpenSSL side.
uint8_t md[MUX_MAX_DIGEST_LENGTH];
unsigned int len = 0;
std::vector<size_t> lens(nfargs > 1 ? nfargs - 1 : 0);
for (int i = 1; i < nfargs; i++)
{
lens[i-1] = strlen(reinterpret_cast<const char *>(fargs[i]));
}
if (mux_digest(fargs[0], const_cast<const UTF8 **>(fargs + 1), lens.data(),
nfargs - 1, md, &len))
{
safe_hex(md, len, true, buff, bufc);
}
else
{
safe_str(S_("#-1 UNSUPPORTED DIGEST TYPE"), buff, bufc);
}
#endif // UNIX_DIGEST
}