/*! \file funmath.cpp * \brief MUX math function handlers. * */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "sha1.h" #include #include #include #include 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(fargs[0]), reinterpret_cast(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(fargs[0]), reinterpret_cast(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(, [, ]) // 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(g_aDoubles[0]); for (int i = 1; i < n; i++) { int64_t divisor = static_cast(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(, [, ]) // // 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 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(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(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(a) << b; safe_i64toa(static_cast(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(mux_atoi64(fargs[0])); safe_i64toa(static_cast(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(mux_atoi64(fargs[0])); safe_i64toa(static_cast(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 v1(new UTF8*[(LBUF_SIZE+1)/2]); std::unique_ptr 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 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 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(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 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 lens(nfargs); for (int i = 0; i < nfargs; i++) { lens[i] = strlen(reinterpret_cast(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(fargs[0]), nullptr); #else const EVP_MD *mp = EVP_get_digestbyname(reinterpret_cast(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(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 lens(nfargs > 1 ? nfargs - 1 : 0); for (int i = 1; i < nfargs; i++) { lens[i-1] = strlen(reinterpret_cast(fargs[i])); } if (mux_digest(fargs[0], const_cast(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 }