#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "lib/mlr_globals.h"
#include "lib/mlrutil.h"
#include "lib/mlrdatetime.h"
#include "lib/mlrregex.h"
#include "lib/mvfuncs.h"
#include "lib/utf8.h"
#include "lib/string_builder.h"

// ================================================================
// See important notes at the top of mlrval.h.
// ================================================================

typedef int mv_i_nn_comparator_func_t(mv_t* pa, mv_t* pb);
typedef int mv_i_xx_comparator_func_t(const mv_t* pa, const mv_t* pb);

// ----------------------------------------------------------------
// Keystroke-savers for disposition matrices:

// Unary:
static mv_t _1u___(mv_t* pa) {
	return *pa;
}
static mv_t _absn1(mv_t* pa) {
	return mv_absent();
}
static mv_t _emt1(mv_t* pa) {
	return mv_empty();
}
static mv_t _err1(mv_t* pa) {
	return mv_error();
}

// Binary:
static mv_t _absn(mv_t* pa, mv_t* pb) {
	return mv_absent();
}
static mv_t _void(mv_t* pa, mv_t* pb) {
	return mv_empty();
}
static mv_t _erro(mv_t* pa, mv_t* pb) {
	return mv_error();
}

static mv_t _1___(mv_t* pa, mv_t* pb) {
	return *pa;
}
static mv_t _2___(mv_t* pa, mv_t* pb) {
	return *pb;
}
static mv_t _1f__(mv_t* pa, mv_t* pb) {
	mv_free(pb);
	return *pa;
}
static mv_t _2f__(mv_t* pa, mv_t* pb) {
	mv_free(pa);
	return *pb;
}
static mv_t _s1__(mv_t* pa, mv_t* pb) {
	return s_x_string_func(pa);
}
static mv_t _s2__(mv_t* pa, mv_t* pb) {
	return s_x_string_func(pb);
}
static mv_t _f0__(mv_t* pa, mv_t* pb) {
	return mv_from_float(0.0);
}
static mv_t _i0__(mv_t* pa, mv_t* pb) {
	return mv_from_int(0LL);
}

// ----------------------------------------------------------------
static mv_t dot_strings(char* string1, char* string2) {
	int len1 = strlen(string1);
	int len2 = strlen(string2);
	int len3 = len1 + len2 + 1; // for the null-terminator byte
	char* string3 = mlr_malloc_or_die(len3);
	strcpy(&string3[0], string1);
	strcpy(&string3[len1], string2);
	return mv_from_string_with_free(string3);
}

mv_t dot_s_ss(mv_t* pval1, mv_t* pval2) {
	mv_t rv = dot_strings(pval1->u.strv, pval2->u.strv);
	mv_free(pval1);
	mv_free(pval2);
	return rv;
}

mv_t dot_s_xs(mv_t* pval1, mv_t* pval2) {
	mv_t sval1 = s_x_string_func(pval1);
	mv_free(pval1);
	mv_t rv = dot_strings(sval1.u.strv, pval2->u.strv);
	mv_free(&sval1);
	mv_free(pval2);
	return rv;
}

mv_t dot_s_sx(mv_t* pval1, mv_t* pval2) {
	mv_t sval2 = s_x_string_func(pval2);
	mv_free(pval2);
	mv_t rv = dot_strings(pval1->u.strv, sval2.u.strv);
	mv_free(pval1);
	mv_free(&sval2);
	return rv;
}

mv_t dot_s_xx(mv_t* pval1, mv_t* pval2) {
	mv_t sval1 = s_x_string_func(pval1);
	mv_t sval2 = s_x_string_func(pval2);
	mv_t rv = dot_strings(sval1.u.strv, sval2.u.strv);
	mv_free(&sval1);
	mv_free(&sval2);
	return rv;
}

static mv_binary_func_t* dot_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING    INT       FLOAT     BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro,    _erro,    _erro,    _erro},
	/*ABSENT*/ {_erro, _absn, _void, _2___,    _s2__,    _s2__,    _s2__},
	/*EMPTY*/  {_erro, _void, _void, _2___,    _s2__,    _s2__,    _s2__},
	/*STRING*/ {_erro, _1___, _1___, dot_s_ss, dot_s_sx, dot_s_sx, dot_s_sx},
	/*INT*/    {_erro, _s1__, _s1__, dot_s_xs, dot_s_xx, dot_s_xx, dot_s_xx},
	/*FLOAT*/  {_erro, _s1__, _s1__, dot_s_xs, dot_s_xx, dot_s_xx, dot_s_xx},
	/*BOOL*/   {_erro, _s1__, _s1__, dot_s_xs, dot_s_xx, dot_s_xx, dot_s_xx},
	};

mv_t s_xx_dot_func(mv_t* pval1, mv_t* pval2) { return (dot_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
mv_t sub_no_precomp_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	regex_t regex;
	string_builder_t *psb = sb_alloc(MV_SB_ALLOC_LENGTH);
	mv_t rv = sub_precomp_func(pval1, regcomp_or_die(&regex, pval2->u.strv, 0), psb, pval3);
	sb_free(psb);
	regfree(&regex);
	mv_free(pval2);
	return rv;
}

// ----------------------------------------------------------------
// Example:
// * pval1->u.strv = "hello"
// * regex = "l+"
// * pval3->u.strv = "yyy"
//
// *  len1 = 2 = length of "he"
// * olen2 = 2 = length of "ll"
// * nlen2 = 3 = length of "yyy"
// *  len3 = 1 = length of "o"
// *  len4 = 6 = 2+3+1

mv_t sub_precomp_func(mv_t* pval1, regex_t* pregex, string_builder_t* psb, mv_t* pval3) {
	int matched      = FALSE;
	int all_captured = FALSE;
	char* input      = pval1->u.strv;
	char* output     = regex_sub(input, pregex, psb, pval3->u.strv, &matched, &all_captured);

	mv_free(pval1);
	mv_free(pval3);
	return mv_from_string_with_free(output);
}

// ----------------------------------------------------------------
// Example:
// * pval1->u.strv = "hello"
// * regex = "l+"
// * pval3->u.strv = "yyy"
//
// *  len1 = 2 = length of "he"
// * olen2 = 2 = length of "ll"
// * nlen2 = 3 = length of "yyy"
// *  len3 = 1 = length of "o"
// *  len4 = 6 = 2+3+1

mv_t gsub_no_precomp_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	regex_t regex;
	string_builder_t *psb = sb_alloc(MV_SB_ALLOC_LENGTH);
	mv_t rv = gsub_precomp_func(pval1, regcomp_or_die(&regex, pval2->u.strv, 0), psb, pval3);
	sb_free(psb);
	regfree(&regex);
	mv_free(pval2);
	return rv;
}

mv_t gsub_precomp_func(mv_t* pval1, regex_t* pregex, string_builder_t* psb, mv_t* pval3) {
	int matched      = FALSE;
	int all_captured = FALSE;
	char* input      = pval1->u.strv;
	char free_flags  = NO_FREE;
	char* output     = regex_gsub(input, pregex, psb, pval3->u.strv, &matched, &all_captured, &free_flags);

	mv_free(pval1);
	mv_free(pval3);
	return mv_from_string(output, free_flags);
}

// ----------------------------------------------------------------
mv_t regextract_no_precomp_func(mv_t* pval1, mv_t* pval2) {
	regex_t regex;
	mv_t rv = regextract_precomp_func(pval1, regcomp_or_die(&regex, pval2->u.strv, 0));
	regfree(&regex);
	mv_free(pval2);
	return rv;
}

// ----------------------------------------------------------------
mv_t regextract_precomp_func(mv_t* pval1, regex_t* pregex) {
	char* input  = pval1->u.strv;
	char* output = regextract(input, pregex);

	mv_free(pval1);
	if (output == NULL) {
		return mv_absent();
	} else {
		return mv_from_string_with_free(output);
	}
}

// ----------------------------------------------------------------
mv_t regextract_or_else_no_precomp_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	regex_t regex;
	mv_t rv = regextract_or_else_precomp_func(pval1, regcomp_or_die(&regex, pval2->u.strv, 0), pval3);
	regfree(&regex);
	mv_free(pval2);
	return rv;
}

// ----------------------------------------------------------------
mv_t regextract_or_else_precomp_func(mv_t* pval1, regex_t* pregex, mv_t* pval3) {
	char* input  = pval1->u.strv;
	char* default_value  = pval3->u.strv;
	char* output = regextract_or_else(input, pregex, default_value);

	mv_free(pval1);
	mv_free(pval3);
	return mv_from_string_with_free(output);
}

// ----------------------------------------------------------------
// String-substitution with no regexes or special characters.
// It is assumed that all inputs have already been checked to be strings.
mv_t s_sss_ssub_func(mv_t* pmvinput, mv_t* pmvold, mv_t* pmvnew) {
	char* pinput = pmvinput->u.strv;
	char* pold = pmvold->u.strv;
	char* pnew = pmvnew->u.strv;

	char* pmatch = strstr(pinput, pold);

	if (pmatch == NULL) {
		mv_free(pmvold);
		mv_free(pmvnew);
		return *pmvinput;
	} else {
		// Example:
		// input: aaaaOOObbbbb
		// old:   OOO
		// new:   NNNNN
		// Output length: strlen(aaaa) + strlen(NNNNN) + strlen(bbbbb)

		// Compute lengths
		int input_length = strlen(pinput);
		int old_length = strlen(pold);
		int new_length = strlen(pnew);
		int output_length = input_length - old_length + new_length + 1;
		int pre_length = pmatch - pinput; // the "aaaa" part
		int post_length = input_length - pre_length - old_length; // the "bbbbb" part

		// Allocate output
		char* poutput = mlr_malloc_or_die(output_length);
		char* p = poutput;

		// Populate output
		strncpy(p, pinput, pre_length);
		p += pre_length;

		strcpy(p, pnew);
		p += new_length;

		strcpy(p, &pinput[pre_length + old_length]);
		p += post_length;

		*p = 0;

		mv_free(pmvinput);
		mv_free(pmvold);
		mv_free(pmvnew);
		return mv_from_string(poutput, FREE_ENTRY_VALUE);
	}
}

// ----------------------------------------------------------------
// https://en.wikipedia.org/wiki/Hamming_weight

static const unsigned long long _m1  = 0x5555555555555555;
static const unsigned long long _m2  = 0x3333333333333333;
static const unsigned long long _m4  = 0x0f0f0f0f0f0f0f0f;
static const unsigned long long _m8  = 0x00ff00ff00ff00ff;
static const unsigned long long _m16 = 0x0000ffff0000ffff;
static const unsigned long long _m32 = 0x00000000ffffffff;

mv_t i_i_bitcount_func(mv_t* pval1) {
	unsigned long long a = pval1->u.intv;
	a = (a & _m1 ) + ((a >>  1) & _m1 );
	a = (a & _m2 ) + ((a >>  2) & _m2 );
	a = (a & _m4 ) + ((a >>  4) & _m4 );
	a = (a & _m8 ) + ((a >>  8) & _m8 );
	a = (a & _m16) + ((a >> 16) & _m16);
	a = (a & _m32) + ((a >> 32) & _m32);
	return mv_from_int((long long)a);
}

// ----------------------------------------------------------------
mv_t i_iii_modadd_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	long long m = pval3->u.intv;
	if (m <= 0LL)
		return mv_error();
	long long a = pval1->u.intv % m;
	if (a < 0LL)
		a += m; // crazy C-language mod operator
	long long b = pval2->u.intv % m;
	if (b < 0LL)
		b += m;
	long long c = (a + b) % m;
	return mv_from_int(c);
}

mv_t i_iii_modsub_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	long long m = pval3->u.intv;
	if (m <= 0LL)
		return mv_error();
	long long a = pval1->u.intv % m;
	if (a < 0LL)
		a += m; // crazy C-language mod operator
	long long b = pval2->u.intv % m;
	if (b < 0LL)
		b += m;
	long long c = (a - b) % m;
	if (c < 0LL)
		c += m;
	return mv_from_int(c);
}

mv_t i_iii_modmul_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	long long m = pval3->u.intv;
	if (m <= 0LL)
		return mv_error();
	long long a = pval1->u.intv % m;
	if (a < 0LL)
		a += m; // crazy C-language mod operator
	long long b = pval2->u.intv % m;
	if (b < 0LL)
		b += m;
	long long c = (a * b) % m;
	return mv_from_int(c);
}

mv_t i_iii_modexp_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	long long m = pval3->u.intv;
	if (m <= 0LL)
		return mv_error();
	long long a = pval1->u.intv % m;
	if (a < 0LL)
		a += m; // crazy C-language mod operator

	long long e = pval2->u.intv;

	long long c = 1LL;
	if (e == 1LL) {
		c = a;
	} else if (e == 0LL) {
		c = 1LL;
	} else if (e > 0) {
		long long ap = a;
		c = 1LL;
		unsigned long long u = (unsigned long long)e;

		// repeated-squaring algorithm
		while (u != 0) {
			if ((u & 1LL) == 1LL) {
				c = (c * ap) % m;
			}
			u >>= 1;
			ap = (ap * ap) % m;
		}
	} else {
		return mv_error();
	}

	return mv_from_int(c);
}

// ----------------------------------------------------------------
mv_t i_s_strlen_func(mv_t* pval1) {
	mv_t rv = mv_from_int(strlen_for_utf8_display(pval1->u.strv));
	mv_free(pval1);
	return rv;
}

mv_t s_x_typeof_func(mv_t* pval1) {
	mv_t rv = mv_from_string(mt_describe_type(pval1->type), NO_FREE);
	mv_free(pval1);
	return rv;
}

// ----------------------------------------------------------------
mv_t s_s_tolower_func(mv_t* pval1) {
	char* string = mlr_strdup_or_die(pval1->u.strv);
#if 0
	// ASCII only
	for (char* c = string; *c; c++)
		*c = tolower((unsigned char)*c);
#else
	// UTF-8
	utf8lwr(string);
#endif
	mv_free(pval1);
	pval1->u.strv = NULL;

	return mv_from_string_with_free(string);
}

mv_t s_s_toupper_func(mv_t* pval1) {
	char* string = mlr_strdup_or_die(pval1->u.strv);
#if 0
	// ASCII only
	for (char* c = string; *c; c++)
		*c = toupper((unsigned char)*c);
#else
	// UTF-8
	utf8upr(string);
#endif
	mv_free(pval1);
	pval1->u.strv = NULL;

	return mv_from_string_with_free(string);
}

mv_t s_s_capitalize_func(mv_t* pval1) {
	char* string = mlr_strdup_or_die(pval1->u.strv);

	if (*string) {
#if 0
		// ASCII
		*string = toupper((unsigned char)*string);
#else
		// UTF-8
		utf8upr1(string);
#endif
	}

	mv_free(pval1);
	pval1->u.strv = NULL;

	return mv_from_string_with_free(string);
}

mv_t s_s_system_func(mv_t* pval1) {
	char* cmd = pval1->u.strv;

	mv_t retval = mv_from_string_no_free("error-running-system-command");
	string_builder_t* psb = sb_alloc(100);
    char buffer[128];
	FILE* pipe = popen(cmd, "r");
	if (pipe != NULL) {
		while (fgets(buffer, sizeof buffer, pipe) != NULL) {
			sb_append_string(psb, buffer);
		}
		pclose(pipe);
		char* output_string = sb_finish(psb);

		// xxx make a windows-friendly lib func for chomp
		int len = strlen(output_string);
		if (len > 0) {
			if (output_string[len-1] == '\n') {
				output_string[len-1] = 0;
			}
		}

		retval = mv_from_string_with_free(output_string);
	}
	sb_free(psb);

	mv_free(pval1);
	pval1->u.strv = NULL;

	return retval;
}

mv_t s_si_truncate_func(mv_t* pval1, mv_t* pval2) {
	char* string = pval1->u.strv;
	int len = strlen(string);
	int maxlen = pval2->u.intv;

	if (len <= maxlen) {
		return *pval1;
	} else {
		char* buf = mlr_malloc_or_die(maxlen+1);
		strncpy(buf, string, maxlen);
		buf[maxlen] = 0;
		mv_free(pval1);
		pval1->u.strv = NULL;
		return mv_from_string_with_free(buf);
	}
}

// ----------------------------------------------------------------
mv_t s_s_lstrip_func(mv_t* pval1) {
	if (!isspace(pval1->u.strv[0])) {
		return *pval1;
	} else {
		char* p = pval1->u.strv;
		while (isspace(*p)) {
			p++;
		}
		char* retval = mlr_strdup_or_die(p);
		mv_free(pval1);
		return mv_from_string(retval, FREE_ENTRY_VALUE);
	}
}

mv_t s_s_rstrip_func(mv_t* pval1) {
	char* start = pval1->u.strv;
	int oldlen = strlen(start);
	char* last_non_space = &start[oldlen-1];
	while ((start <= last_non_space) && isspace(*last_non_space))
		last_non_space--;
	if (last_non_space < start) {
		mv_free(pval1);
		return mv_empty();
	} else {
		int newlen = (last_non_space - start) + 1;
		char* retval = mlr_malloc_or_die(newlen + 1);
		memcpy(retval, start, newlen);
		retval[newlen] = 0;
		mv_free(pval1);
		return mv_from_string(retval, FREE_ENTRY_VALUE);
	}
}

mv_t s_s_strip_func(mv_t* pval1) {
	mv_t temp = s_s_rstrip_func(pval1);
	return s_s_lstrip_func(&temp);
}

mv_t s_s_collapse_whitespace_func(mv_t* pval1) {
	int len = strlen(pval1->u.strv);
	char* retval = mlr_malloc_or_die(len+1);
	char* pdst = retval;
	int last_was_space = FALSE;
	for (char* psrc = pval1->u.strv; *psrc; psrc++) {
		int current_is_space = isspace(*psrc);
		if (last_was_space && current_is_space) {
		} else {
			*pdst = *psrc;
			pdst++;
		}
		last_was_space = current_is_space;
	}
	*pdst = 0;
	mv_free(pval1);
	return mv_from_string(retval, FREE_ENTRY_VALUE);
}

mv_t s_s_clean_whitespace_func(mv_t* pval1) {
	mv_t temp = s_s_collapse_whitespace_func(pval1);
	return s_s_strip_func(&temp);
}

// ----------------------------------------------------------------
// Precondition: psec is either int or float.
mv_t time_string_from_seconds(mv_t* psec, char* format,
	timezone_handling_t timezone_handling)
{
	double seconds_since_the_epoch = 0.0;
	if (psec->type == MT_FLOAT) {
		if (isinf(psec->u.fltv) || isnan(psec->u.fltv)) {
			return mv_error();
		}
		seconds_since_the_epoch = psec->u.fltv;
	} else {
		seconds_since_the_epoch = psec->u.intv;
	}

	char* string = mlr_alloc_time_string_from_seconds(seconds_since_the_epoch, format,
		timezone_handling);

	return mv_from_string_with_free(string);
}

// ----------------------------------------------------------------
static mv_t sec2gmt_s_n(mv_t* pa) {
	return time_string_from_seconds(pa, ISO8601_TIME_FORMAT, TIMEZONE_HANDLING_GMT);
}

static mv_unary_func_t* sec2gmt_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _1u___,
	/*INT*/    sec2gmt_s_n,
	/*FLOAT*/  sec2gmt_s_n,
	/*BOOL*/   _1u___,
};
mv_t s_x_sec2gmt_func(mv_t* pval1) { return (sec2gmt_dispositions[pval1->type])(pval1); }

// Precondition: val2 is already asserted int
static mv_t sec2gmt_s_ni(mv_t* pa, mv_t* pb) {
	switch (pb->u.intv) {
	case 1: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_1, TIMEZONE_HANDLING_GMT); break;
	case 2: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_2, TIMEZONE_HANDLING_GMT); break;
	case 3: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_3, TIMEZONE_HANDLING_GMT); break;
	case 4: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_4, TIMEZONE_HANDLING_GMT); break;
	case 5: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_5, TIMEZONE_HANDLING_GMT); break;
	case 6: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_6, TIMEZONE_HANDLING_GMT); break;
	case 7: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_7, TIMEZONE_HANDLING_GMT); break;
	case 8: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_8, TIMEZONE_HANDLING_GMT); break;
	case 9: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_9, TIMEZONE_HANDLING_GMT); break;
	default: return mv_error();
	}
}

static mv_binary_func_t* sec2gmtn_dispositions[MT_DIM] = {
	/*ERROR*/  _erro,
	/*ABSENT*/ _absn,
	/*EMPTY*/  _void,
	/*STRING*/ _1___,
	/*INT*/    sec2gmt_s_ni,
	/*FLOAT*/  sec2gmt_s_ni,
	/*BOOL*/   _1___,
};
mv_t s_xi_sec2gmt_func(mv_t* pval1, mv_t* pval2) { return (sec2gmtn_dispositions[pval1->type])(pval1, pval2); }

// ----------------------------------------------------------------
static mv_t sec2gmtdate_s_n(mv_t* pa) {
	return time_string_from_seconds(pa, ISO8601_DATE_FORMAT, TIMEZONE_HANDLING_GMT);
}

static mv_unary_func_t* sec2gmtdate_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _1u___,
	/*INT*/    sec2gmtdate_s_n,
	/*FLOAT*/  sec2gmtdate_s_n,
	/*BOOL*/   _1u___,
};

mv_t s_x_sec2gmtdate_func(mv_t* pval1) { return (sec2gmtdate_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t sec2localtime_s_n(mv_t* pa) {
	return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT, TIMEZONE_HANDLING_LOCAL);
}

static mv_unary_func_t* sec2localtime_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _1u___,
	/*INT*/    sec2localtime_s_n,
	/*FLOAT*/  sec2localtime_s_n,
	/*BOOL*/   _1u___,
};
mv_t s_x_sec2localtime_func(mv_t* pval1) { return (sec2localtime_dispositions[pval1->type])(pval1); }

// Precondition: val2 is already asserted int
static mv_t sec2localtime_s_ni(mv_t* pa, mv_t* pb) {
	switch (pb->u.intv) {
	case 1: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_1, TIMEZONE_HANDLING_LOCAL); break;
	case 2: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_2, TIMEZONE_HANDLING_LOCAL); break;
	case 3: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_3, TIMEZONE_HANDLING_LOCAL); break;
	case 4: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_4, TIMEZONE_HANDLING_LOCAL); break;
	case 5: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_5, TIMEZONE_HANDLING_LOCAL); break;
	case 6: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_6, TIMEZONE_HANDLING_LOCAL); break;
	case 7: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_7, TIMEZONE_HANDLING_LOCAL); break;
	case 8: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_8, TIMEZONE_HANDLING_LOCAL); break;
	case 9: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_9, TIMEZONE_HANDLING_LOCAL); break;
	default: return mv_error();
	}
}

static mv_binary_func_t* sec2localn_dispositions[MT_DIM] = {
	/*ERROR*/  _erro,
	/*ABSENT*/ _absn,
	/*EMPTY*/  _void,
	/*STRING*/ _1___,
	/*INT*/    sec2localtime_s_ni,
	/*FLOAT*/  sec2localtime_s_ni,
	/*BOOL*/   _1___,
};
mv_t s_xi_sec2localtime_func(mv_t* pval1, mv_t* pval2) { return (sec2localn_dispositions[pval1->type])(pval1, pval2); }

// ----------------------------------------------------------------
static mv_t sec2localdate_s_n(mv_t* pa) {
	return time_string_from_seconds(pa, ISO8601_DATE_FORMAT, TIMEZONE_HANDLING_LOCAL);
}

static mv_unary_func_t* sec2localdate_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _1u___,
	/*INT*/    sec2localdate_s_n,
	/*FLOAT*/  sec2localdate_s_n,
	/*BOOL*/   _1u___,
};

mv_t s_x_sec2localdate_func(mv_t* pval1) { return (sec2localdate_dispositions[pval1->type])(pval1); }


// ----------------------------------------------------------------
mv_t s_ns_strftime_func(mv_t* pval1, mv_t* pval2) {
	mv_t rv = time_string_from_seconds(pval1, pval2->u.strv, TIMEZONE_HANDLING_GMT);
	mv_free(pval2);
	return rv;
}

// ----------------------------------------------------------------
mv_t s_ns_strftime_local_func(mv_t* pval1, mv_t* pval2) {
	mv_t rv = time_string_from_seconds(pval1, pval2->u.strv, TIMEZONE_HANDLING_LOCAL);
	mv_free(pval2);
	return rv;
}

// ----------------------------------------------------------------
static mv_t seconds_from_time_string(char* string, char* format,
	timezone_handling_t timezone_handling)
{
	if (*string == '\0') {
		return mv_empty();
	} else {
		return mv_from_float(mlr_seconds_from_time_string(string, format, timezone_handling));
	}
}

mv_t i_s_gmt2sec_func(mv_t* pval1) {
	mv_t rv = seconds_from_time_string(pval1->u.strv, ISO8601_TIME_FORMAT, TIMEZONE_HANDLING_GMT);
	mv_free(pval1);
	return rv;
}

mv_t i_s_localtime2sec_func(mv_t* pval1) {
	mv_t rv = seconds_from_time_string(pval1->u.strv, ISO8601_LOCAL_TIME_FORMAT, TIMEZONE_HANDLING_LOCAL);
	mv_free(pval1);
	return rv;
}

mv_t i_ss_strptime_func(mv_t* pval1, mv_t* pval2) {
	mv_t rv = seconds_from_time_string(pval1->u.strv, pval2->u.strv, TIMEZONE_HANDLING_GMT);
	mv_free(pval1);
	mv_free(pval2);
	return rv;
}

mv_t i_ss_strptime_local_func(mv_t* pval1, mv_t* pval2) {
	mv_t rv = seconds_from_time_string(pval1->u.strv, pval2->u.strv, TIMEZONE_HANDLING_LOCAL);
	mv_free(pval1);
	mv_free(pval2);
	return rv;
}

// ----------------------------------------------------------------
static void split_ull_to_hms(long long u, long long* ph, long long* pm, long long* ps) {
	long long h = 0LL, m = 0LL, s = 0LL;
	long long sign = 1LL;
	if (u < 0LL) {
		u = -u;
		sign = -1LL;
	}
	s = u % 60LL;
	u = u / 60LL;
	if (u == 0LL) {
		s = s * sign;
	} else {
		m = u % 60LL;
		u = u / 60LL;
		if (u == 0LL) {
			m = m * sign;
		} else {
			h = u * sign;
		}
	}
	*ph = h;
	*pm = m;
	*ps = s;
}

static void split_ull_to_dhms(long long u, long long* pd, long long* ph, long long* pm, long long* ps) {
	long long d = 0LL, h = 0LL, m = 0LL, s = 0LL;
	long long sign = 1LL;
	if (u < 0LL) {
		u = -u;
		sign = -1LL;
	}
	s = u % 60LL;
	u = u / 60LL;
	if (u == 0LL) {
		s = s * sign;
	} else {
		m = u % 60LL;
		u = u / 60LL;
		if (u == 0LL) {
			m = m * sign;
		} else {
			h = u % 24LL;
			u = u / 24LL;
			if (u == 0LL) {
				h = h * sign;
			} else {
				d = u * sign;
			}
		}
	}
	*pd = d;
	*ph = h;
	*pm = m;
	*ps = s;
}

mv_t s_i_sec2hms_func(mv_t* pval1) {
	long long u = pval1->u.intv;
	long long h, m, s;
	char* fmt = "%02lld:%02lld:%02lld";
	if (u < 0) {
		u = -u;
		fmt = "-%02lld:%02lld:%02lld";
	}
	split_ull_to_hms(u, &h, &m, &s);
	int n = snprintf(NULL, 0, fmt, h, m, s);
	char* string = mlr_malloc_or_die(n+1);
	sprintf(string, fmt, h, m, s);
	return mv_from_string_with_free(string);
}

mv_t s_f_fsec2hms_func(mv_t* pval1) {
	double v = fabs(pval1->u.fltv);
	long long h, m, s;
	char* fmt = "%lld:%02lld:%09.6lf";
	long long u = (long long)trunc(v);
	double f = v - u;
	if (pval1->u.fltv < 0.0) {
		fmt = "-%02lld:%02lld:%09.6lf";
	}
	split_ull_to_hms(u, &h, &m, &s);
	int n = snprintf(NULL, 0, fmt, h, m, s+f);
	char* string = mlr_malloc_or_die(n+1);
	sprintf(string, fmt, h, m, s+f);
	return mv_from_string_with_free(string);
}

mv_t s_i_sec2dhms_func(mv_t* pval1) {
	long long u = pval1->u.intv;
	long long d, h, m, s;
	split_ull_to_dhms(u, &d, &h, &m, &s);
	if (d != 0.0) {
		char* fmt = "%lldd%02lldh%02lldm%02llds";
		int n = snprintf(NULL, 0, fmt, d, h, m, s);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, d, h, m, s);
		return mv_from_string_with_free(string);
	} else if (h != 0.0) {
		char* fmt = "%lldh%02lldm%02llds";
		int n = snprintf(NULL, 0, fmt, h, m, s);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, h, m, s);
		return mv_from_string_with_free(string);
	} else if (m != 0.0) {
		char* fmt = "%lldm%02llds";
		int n = snprintf(NULL, 0, fmt, m, s);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, m, s);
		return mv_from_string_with_free(string);
	} else {
		char* fmt = "%llds";
		int n = snprintf(NULL, 0, fmt, s);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, s);
		return mv_from_string_with_free(string);
	}
}

mv_t s_f_fsec2dhms_func(mv_t* pval1) {
	double v = fabs(pval1->u.fltv);
	long long sign = pval1->u.fltv < 0.0 ? -1LL : 1LL;
	long long d, h, m, s;
	long long u = (long long)trunc(v);
	double f = v - u;
	split_ull_to_dhms(u, &d, &h, &m, &s);
	if (d != 0.0) {
		d = sign * d;
		char* fmt = "%lldd%02lldh%02lldm%09.6lfs";
		int n = snprintf(NULL, 0, fmt, d, h, m, s+f);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, d, h, m, s+f);
		return mv_from_string_with_free(string);
	} else if (h != 0.0) {
		h = sign * h;
		char* fmt = "%lldh%02lldm%09.6lfs";
		int n = snprintf(NULL, 0, fmt, h, m, s+f);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, h, m, s+f);
		return mv_from_string_with_free(string);
	} else if (m != 0.0) {
		m = sign * m;
		char* fmt = "%lldm%09.6lfs";
		int n = snprintf(NULL, 0, fmt, m, s+f);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, m, s+f);
		return mv_from_string_with_free(string);
	} else {
		s = sign * s;
		f = sign * f;
		char* fmt = "%.6lfs";
		int n = snprintf(NULL, 0, fmt, s+f);
		char* string = mlr_malloc_or_die(n+1);
		sprintf(string, fmt, s+f);
		return mv_from_string_with_free(string);
	}
}

// ----------------------------------------------------------------
mv_t i_s_hms2sec_func(mv_t* pval1) {
	long long h = 0LL, m = 0LL, s = 0LL;
	long long sec = 0LL;
	char* p = pval1->u.strv;
	long long sign = 1LL;
	if (*p == '-') {
		p++;
		sign = -1LL;
	}
	if (sscanf(p, "%lld:%lld:%lld", &h, &m, &s) == 3) {
		if (h >= 0LL)
			sec = 3600LL*h + 60LL*m + s;
		else
			sec = -(-3600LL*h + 60LL*m + s);
	} else if (sscanf(p, "%lld:%lld", &m, &s) == 2) {
		if (m >= 0LL)
			sec = 60LL*m + s;
		else
			sec = -(-60LL*m + s);
	} else if (sscanf(p, "%lld", &s) == 1) {
		sec = s;
	} else {
		mv_free(pval1);
		return mv_error();
	}
	mv_free(pval1);
	return mv_from_int(sec * sign);
}

mv_t f_s_hms2fsec_func(mv_t* pval1) {
	long long h = 0LL, m = 0LL;
	double s = 0.0;
	double sec = 0.0;
	char* p = pval1->u.strv;
	double sign = 1.0;
	if (*p == '-') {
		p++;
		sign = -1.0;
	}
	if (sscanf(p, "%lld:%lld:%lf", &h, &m, &s) == 3) {
		sec = 3600*h + 60*m + s;
	} else if (sscanf(p, "%lld:%lf", &m, &s) == 2) {
		sec = 60*m + s;
	} else if (sscanf(p, "%lf", &s) == 2) {
		sec = s;
	} else {
		mv_free(pval1);
		return mv_error();
	}
	mv_free(pval1);
	return mv_from_float(sec * sign);
}

mv_t i_s_dhms2sec_func(mv_t* pval1) {
	long long d = 0LL, h = 0LL, m = 0LL, s = 0LL;
	long long sec = 0LL;
	char* p = pval1->u.strv;
	long long sign = 1LL;
	if (*p == '-') {
		p++;
		sign = -1LL;
	}
	if (sscanf(p, "%lldd%lldh%lldm%llds", &d, &h, &m, &s) == 4) {
		sec = 86400*d + 3600*h + 60*m + s;
	} else if (sscanf(p, "%lldh%lldm%llds", &h, &m, &s) == 3) {
		sec = 3600*h + 60*m + s;
	} else if (sscanf(p, "%lldm%llds", &m, &s) == 2) {
		sec = 60*m + s;
	} else if (sscanf(p, "%llds", &s) == 1) {
		sec = s;
	} else {
		mv_free(pval1);
		return mv_error();
	}
	mv_free(pval1);
	return mv_from_int(sec * sign);
}

mv_t f_s_dhms2fsec_func(mv_t* pval1) {
	long long d = 0LL, h = 0LL, m = 0LL;
	double s = 0.0;
	double sec = 0.0;
	char* p = pval1->u.strv;
	long long sign = 1.0;
	if (*p == '-') {
		p++;
		sign = -1.0;
	}
	if (sscanf(p, "%lldd%lldh%lldm%lfs", &d, &h, &m, &s) == 4) {
		sec = 86400*d + 3600*h + 60*m + s;
	} else if (sscanf(p, "%lldh%lldm%lfs", &h, &m, &s) == 3) {
		sec = 3600*h + 60*m + s;
	} else if (sscanf(p, "%lldm%lfs", &m, &s) == 2) {
		sec = 60*m + s;
	} else if (sscanf(p, "%lfs", &s) == 1) {
		sec = s;
	} else {
		mv_free(pval1);
		return mv_error();
	}
	mv_free(pval1);
	return mv_from_float(sec * sign);
}

// ================================================================
static mv_t plus_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a + b);
}
static mv_t plus_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a + b);
}
static mv_t plus_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a + b);
}
// Adds & subtracts overflow by at most one bit so it suffices to check
// sign-changes.
static mv_t plus_n_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	long long c = a + b;

	int overflowed = FALSE;
	if (a > 0LL) {
		if (b > 0LL && c < 0LL)
			overflowed = TRUE;
	} else if (a < 0LL) {
		if (b < 0LL && c > 0LL)
			overflowed = TRUE;
	}

	if (overflowed) {
		return mv_from_float((double)a + (double)b);
	} else {
		return mv_from_int(c);
	}
}

static mv_binary_func_t* plus_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT        FLOAT      BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,     _erro,     _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,     _2___,     _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,     _void,     _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,     _erro,     _erro},
	/*INT*/    {_erro, _1___, _void, _erro, plus_n_ii, plus_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, plus_f_fi, plus_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,     _erro,     _erro},
	};

mv_t x_xx_plus_func(mv_t* pval1, mv_t* pval2) { return (plus_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t minus_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a - b);
}
static mv_t minus_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a - b);
}
static mv_t minus_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a - b);
}
// Adds & subtracts overflow by at most one bit so it suffices to check
// sign-changes.
static mv_t minus_n_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	long long c = a - b;

	int overflowed = FALSE;
	if (a > 0LL) {
		if (b < 0LL && c < 0LL)
			overflowed = TRUE;
	} else if (a < 0LL) {
		if (b > 0LL && c > 0LL)
			overflowed = TRUE;
	}

	if (overflowed) {
		return mv_from_float((double)a - (double)b);
	} else {
		return mv_from_int(c);
	}
}

static mv_binary_func_t* minus_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT         FLOAT       BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,      _2___,      _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,      _void,      _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*INT*/    {_erro, _1___, _void, _erro, minus_n_ii, minus_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, minus_f_fi, minus_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	};

mv_t x_xx_minus_func(mv_t* pval1, mv_t* pval2) { return (minus_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t times_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a * b);
}
static mv_t times_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a * b);
}
static mv_t times_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a * b);
}
// Unlike adds & subtracts which overflow by at most one bit, multiplies can
// overflow by a word size. Thus detecting sign-changes does not suffice to
// detect overflow. Instead we test whether the floating-point product exceeds
// the representable integer range. Now 64-bit integers have 64-bit precision
// while IEEE-doubles have only 52-bit mantissas -- so, 53 bits including
// implicit leading one.
//
// The following experiment explicitly demonstrates the resolution at this range:
//
//    64-bit integer     64-bit integer     Casted to double           Back to 64-bit
//        in hex           in decimal                                    integer
// 0x7ffffffffffff9ff 9223372036854774271 9223372036854773760.000000 0x7ffffffffffff800
// 0x7ffffffffffffa00 9223372036854774272 9223372036854773760.000000 0x7ffffffffffff800
// 0x7ffffffffffffbff 9223372036854774783 9223372036854774784.000000 0x7ffffffffffffc00
// 0x7ffffffffffffc00 9223372036854774784 9223372036854774784.000000 0x7ffffffffffffc00
// 0x7ffffffffffffdff 9223372036854775295 9223372036854774784.000000 0x7ffffffffffffc00
// 0x7ffffffffffffe00 9223372036854775296 9223372036854775808.000000 0x8000000000000000
// 0x7ffffffffffffffe 9223372036854775806 9223372036854775808.000000 0x8000000000000000
// 0x7fffffffffffffff 9223372036854775807 9223372036854775808.000000 0x8000000000000000
//
// That is, we cannot check an integer product to see if it is greater than
// 2**63-1 (or is less than -2**63) using integer arithmetic (it may have
// already overflowed) *or* using double-precision (granularity). Instead we
// check if the absolute value of the product exceeds the largest representable
// double less than 2**63. (An alterative would be to do all integer multiplies
// using handcrafted multi-word 128-bit arithmetic).
static mv_t times_n_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;

	double d = (double)a * (double)b;
	if (fabs(d) > 9223372036854774784.0) {
		return mv_from_float(d);
	} else {
		return mv_from_int(a * b);
	}
}

static mv_binary_func_t* times_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT         FLOAT       BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,      _2___,      _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,      _void,      _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*INT*/    {_erro, _1___, _void, _erro, times_n_ii, times_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, times_f_fi, times_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	};

mv_t x_xx_times_func(mv_t* pval1, mv_t* pval2) { return (times_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t divide_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a / b);
}
static mv_t divide_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a / b);
}
static mv_t divide_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a / b);
}
static mv_t divide_n_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	if (b == 0LL) { // Compute inf/nan as with floats rather than fatal runtime FPE on integer divide by zero
		return mv_from_float((double)a / (double)b);
	}
	long long r = a % b;
	// Pythonic division, not C division.
	if (r == 0LL) {
		return mv_from_int(a / b);
	} else {
		return mv_from_float((double)a / (double)b);
	}
}

static mv_binary_func_t* divide_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT          FLOAT        BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _i0__,       _f0__,       _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,       _void,       _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*INT*/    {_erro, _1___, _void, _erro, divide_n_ii, divide_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, divide_f_fi, divide_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	};

mv_t x_xx_divide_func(mv_t* pval1, mv_t* pval2) { return (divide_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t idiv_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(floor(a / b));
}
static mv_t idiv_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(floor(a / b));
}
static mv_t idiv_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(floor(a / b));
}
static mv_t idiv_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	if (b == 0LL) { // Compute inf/nan as with floats rather than fatal runtime FPE on integer divide by zero
		return mv_from_float((double)a / (double)b);
	}
	// Pythonic division, not C division.
	long long q = a / b;
	long long r = a % b;
	if (a < 0) {
		if (b > 0) {
			if (r != 0)
				q--;
		}
	} else {
		if (b < 0) {
			if (r != 0)
				q--;
		}
	}
	return mv_from_int(q);
}

static mv_binary_func_t* idiv_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT        FLOAT      BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,     _erro,     _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _i0__,     _f0__,     _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,     _void,     _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,     _erro,     _erro},
	/*INT*/    {_erro, _1___, _void, _erro, idiv_i_ii, idiv_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, idiv_f_fi, idiv_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,     _erro,     _erro},
	};

mv_t x_xx_int_divide_func(mv_t* pval1, mv_t* pval2) {
	return (idiv_dispositions[pval1->type][pval2->type])(pval1,pval2);
}

// ================================================================
static mv_t oplus_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a + b);
}
static mv_t oplus_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a + b);
}
static mv_t oplus_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a + b);
}
static mv_t oplus_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	long long c = a + b;
	return mv_from_int(c);
}

static mv_binary_func_t* oplus_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT         FLOAT       BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,      _2___,      _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,      _void,      _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*INT*/    {_erro, _1___, _void, _erro, oplus_i_ii, oplus_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, oplus_f_fi, oplus_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	};

mv_t x_xx_oplus_func(mv_t* pval1, mv_t* pval2) { return (oplus_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t ominus_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a - b);
}
static mv_t ominus_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a - b);
}
static mv_t ominus_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a - b);
}
static mv_t ominus_n_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	long long c = a - b;
	return mv_from_int(c);
}

static mv_binary_func_t* ominus_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT          FLOAT        BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,       _2___,       _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,       _void,       _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*INT*/    {_erro, _1___, _void, _erro, ominus_n_ii, ominus_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, ominus_f_fi, ominus_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	};

mv_t x_xx_ominus_func(mv_t* pval1, mv_t* pval2) { return (ominus_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t otimes_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a * b);
}
static mv_t otimes_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a * b);
}
static mv_t otimes_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a * b);
}
static mv_t otimes_n_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	return mv_from_int(a * b);
}

static mv_binary_func_t* otimes_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT          FLOAT        BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,       _2___,       _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,       _void,       _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*INT*/    {_erro, _1___, _void, _erro, otimes_n_ii, otimes_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, otimes_f_fi, otimes_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	};

mv_t x_xx_otimes_func(mv_t* pval1, mv_t* pval2) { return (otimes_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t odivide_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a / b);
}
static mv_t odivide_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a / b);
}
static mv_t odivide_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a / b);
}
static mv_t odivide_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	return mv_from_int(a / b);
}

static mv_binary_func_t* odivide_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT           FLOAT         BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,        _erro,        _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _i0__,        _f0__,        _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,        _void,        _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,        _erro,        _erro},
	/*INT*/    {_erro, _1___, _void, _erro, odivide_i_ii, odivide_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, odivide_f_fi, odivide_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,        _erro,        _erro},
	};

mv_t x_xx_odivide_func(mv_t* pval1, mv_t* pval2) { return (odivide_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t oidiv_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(floor(a / b));
}
static mv_t oidiv_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(floor(a / b));
}
static mv_t oidiv_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(floor(a / b));
}
static mv_t oidiv_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;

	// Pythonic division, not C division.
	long long q = a / b;
	long long r = a % b;
	if (a < 0) {
		if (b > 0) {
			if (r != 0)
				q--;
		}
	} else {
		if (b < 0) {
			if (r != 0)
				q--;
		}
	}
	return mv_from_int(q);
}

static mv_binary_func_t* oidiv_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT         FLOAT       BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _i0__,      _f0__,      _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,      _void,      _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	/*INT*/    {_erro, _1___, _void, _erro, oidiv_i_ii, oidiv_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, oidiv_f_fi, oidiv_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,      _erro,      _erro},
	};

mv_t x_xx_int_odivide_func(mv_t* pval1, mv_t* pval2) {
	return (oidiv_dispositions[pval1->type][pval2->type])(pval1,pval2);
}

// ================================================================
static mv_t mod_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(a - b * floor(a / b));
}
static mv_t mod_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(a - b * floor(a / b));
}
static mv_t mod_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(a - b * floor(a / b));
}
static mv_t mod_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	if (b == 0LL) { // Compute inf/nan as with floats rather than fatal runtime FPE on integer divide by zero
		return mv_from_float((double)a / (double)b);
	}
	long long u = a % b;
	// Pythonic division, not C division.
	if (a >= 0LL) {
		if (b < 0LL) {
			u += b;
		}
	} else {
		if (b >= 0LL) {
			u += b;
		}
	}
	return mv_from_int(u);
}

static mv_binary_func_t* mod_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT       FLOAT     BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,    _erro,    _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _i0__,    _f0__,    _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,    _void,    _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,    _erro,    _erro},
	/*INT*/    {_erro, _1___, _void, _erro, mod_i_ii, mod_f_if, _erro},
	/*FLOAT*/  {_erro, _1___, _void, _erro, mod_f_fi, mod_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,    _erro,    _erro},
	};

mv_t x_xx_mod_func(mv_t* pval1, mv_t* pval2) {
	return (mod_dispositions[pval1->type][pval2->type])(pval1,pval2);
}

// ----------------------------------------------------------------
static mv_t upos_i_i(mv_t* pa) {
	return *pa;
}
static mv_t upos_f_f(mv_t* pa) {
	return *pa;
}

static mv_unary_func_t* upos_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    upos_i_i,
	/*FLOAT*/  upos_f_f,
	/*BOOL*/   _err1,
};

mv_t x_x_upos_func(mv_t* pval1) { return (upos_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t uneg_i_i(mv_t* pa) {
	return mv_from_int(-pa->u.intv);
}
static mv_t uneg_f_f(mv_t* pa) {
	return mv_from_float(-pa->u.fltv);
}

static mv_unary_func_t* uneg_disnegitions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    uneg_i_i,
	/*FLOAT*/  uneg_f_f,
	/*BOOL*/   _err1,
};

mv_t x_x_uneg_func(mv_t* pval1) { return (uneg_disnegitions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t abs_n_f(mv_t* pa) {
	return mv_from_float(fabs(pa->u.fltv));
}
static mv_t abs_n_i(mv_t* pa) {
	return mv_from_int(pa->u.intv < 0LL ? -pa->u.intv : pa->u.intv);
}

static mv_unary_func_t* abs_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    abs_n_i,
	/*FLOAT*/  abs_n_f,
	/*BOOL*/   _err1,
};

mv_t x_x_abs_func(mv_t* pval1) { return (abs_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t sgn_n_f(mv_t* pa) {
	if (pa->u.fltv > 0.0)
		return mv_from_float(1.0);
	if (pa->u.fltv < 0.0)
		return mv_from_float(-1.0);
	return mv_from_float(0.0);
}
static mv_t sgn_n_i(mv_t* pa) {
	if (pa->u.intv > 0LL)
		return mv_from_int(1LL);
	if (pa->u.intv < 0LL)
		return mv_from_int(-1LL);
	return mv_from_int(0LL);
}

static mv_unary_func_t* sgn_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    sgn_n_i,
	/*FLOAT*/  sgn_n_f,
	/*BOOL*/   _err1,
};

mv_t x_x_sgn_func(mv_t* pval1) { return (sgn_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t ceil_n_f(mv_t* pa) {
	return mv_from_float(ceil(pa->u.fltv));
}
static mv_t ceil_n_i(mv_t* pa) {
	return mv_from_int(pa->u.intv);
}

static mv_unary_func_t* ceil_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    ceil_n_i,
	/*FLOAT*/  ceil_n_f,
	/*BOOL*/   _err1,
};

mv_t x_x_ceil_func(mv_t* pval1) { return (ceil_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t floor_n_f(mv_t* pa) {
	return mv_from_float(floor(pa->u.fltv));
}
static mv_t floor_n_i(mv_t* pa) {
	return mv_from_int(pa->u.intv);
}

static mv_unary_func_t* floor_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    floor_n_i,
	/*FLOAT*/  floor_n_f,
	/*BOOL*/   _err1,
};

mv_t x_x_floor_func(mv_t* pval1) { return (floor_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t round_n_f(mv_t* pa) {
	return mv_from_float(round(pa->u.fltv));
}
static mv_t round_n_i(mv_t* pa) {
	return mv_from_int(pa->u.intv);
}

static mv_unary_func_t* round_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ _err1,
	/*INT*/    round_n_i,
	/*FLOAT*/  round_n_f,
	/*BOOL*/   _err1,
};

mv_t x_x_round_func(mv_t* pval1) { return (round_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t roundm_f_ff(mv_t* pa, mv_t* pb) {
	double x = pa->u.fltv;
	double m = pb->u.fltv;
	return mv_from_float(round(x / m) * m);
}
static mv_t roundm_f_fi(mv_t* pa, mv_t* pb) {
	double x = pa->u.fltv;
	double m = (double)pb->u.intv;
	return mv_from_float(round(x / m) * m);
}
static mv_t roundm_f_if(mv_t* pa, mv_t* pb) {
	double x = (double)pa->u.intv;
	double m = pb->u.fltv;
	return mv_from_float(round(x / m) * m);
}
static mv_t roundm_i_ii(mv_t* pa, mv_t* pb) {
	long long x = pa->u.intv;
	long long m = pb->u.intv;
	return mv_from_int((x / m) * m);
}

static mv_binary_func_t* roundm_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT          FLOAT        BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _absn,       _erro,       _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _erro,       _erro,       _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	/*INT*/    {_erro, _absn, _erro, _erro, roundm_i_ii, roundm_f_if, _erro},
	/*FLOAT*/  {_erro, _erro, _erro, _erro, roundm_f_fi, roundm_f_ff, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,       _erro,       _erro},
	};

mv_t x_xx_roundm_func(mv_t* pval1, mv_t* pval2) { return (roundm_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t min_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(fmin(a, b));
}

static mv_t min_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(fmin(a, b));
}

static mv_t min_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(fmin(a, b));
}

static mv_t min_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	return mv_from_int(a < b ? a : b);
}

static mv_t min_b_bb(mv_t* pa, mv_t* pb) {
	int a = pa->u.boolv;
	int b = pb->u.boolv;
	return mv_from_bool(a < b ? a : b);
}

static mv_t min_s_ss(mv_t* pa, mv_t* pb) {
	char* a = pa->u.strv;
	char* b = pb->u.strv;
	int   c = strcmp(a, b);
	if (c <= 0) {
		mv_free(pb);
		return *pa;
	} else {
		mv_free(pa);
		return *pb;
	}
}

// Sort rules (same for min, max, and comparator):
// * NUMERICS < BOOL < STRINGS < ERROR < ABSENT
// * error == error (singleton type)
// * absent == absent (singleton type)
// * string compares on strings
// * numeric compares on numbers
// * false < true
// Exceptions for min & max:
// * absent-null always loses
// * empty-null always loses against numbers
static mv_binary_func_t* min_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING    INT       FLOAT     BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro,    _erro,    _erro,    _erro},
	/*ABSENT*/ {_erro, _absn, _2___, _2f__,    _2___,    _2___,    _2___},
	/*EMPTY*/  {_erro, _1___, _void, _void,    _2___,    _2___,    _2___},
	/*STRING*/ {_erro, _1f__, _void, min_s_ss, _2f__,    _2f__,    _2f__},
	/*INT*/    {_erro, _1___, _1___, _1f__,    min_i_ii, min_f_if, _1___},
	/*FLOAT*/  {_erro, _1___, _1___, _1f__,    min_f_fi, min_f_ff, _1___},
	/*BOOL*/   {_erro, _1___, _1___, _1f__,    _2___,    _2___,    min_b_bb},
	};

mv_t x_xx_min_func(mv_t* pval1, mv_t* pval2) {
	return (min_dispositions[pval1->type][pval2->type])(pval1,pval2);
}

mv_t variadic_min_func(mv_t* pvals, int nvals) {
	if (nvals == 0) {
		return mv_empty();
	} else {
		mv_t rv = pvals[0];
		for (int i = 1; i < nvals; i++) {
			rv = x_xx_min_func(&rv, &pvals[i]);
			// Disposition-matrix entries for min all free their non-return arguments
		}
		return rv;
	}
}

// ----------------------------------------------------------------
static mv_t max_f_ff(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = pb->u.fltv;
	return mv_from_float(fmax(a, b));
}

static mv_t max_f_fi(mv_t* pa, mv_t* pb) {
	double a = pa->u.fltv;
	double b = (double)pb->u.intv;
	return mv_from_float(fmax(a, b));
}

static mv_t max_f_if(mv_t* pa, mv_t* pb) {
	double a = (double)pa->u.intv;
	double b = pb->u.fltv;
	return mv_from_float(fmax(a, b));
}

static mv_t max_i_ii(mv_t* pa, mv_t* pb) {
	long long a = pa->u.intv;
	long long b = pb->u.intv;
	return mv_from_int(a > b ? a : b);
}

static mv_t max_b_bb(mv_t* pa, mv_t* pb) {
	int a = pa->u.boolv;
	int b = pb->u.boolv;
	return mv_from_bool(a > b ? a : b);
}

static mv_t max_s_ss(mv_t* pa, mv_t* pb) {
	char* a = pa->u.strv;
	char* b = pb->u.strv;
	int   c = strcmp(a, b);
	if (c >= 0) {
		mv_free(pb);
		return *pa;
	} else {
		mv_free(pa);
		return *pb;
	}
}

// Sort rules (same for min, max, and comparator):
// * NUMERICS < BOOL < STRINGS < ERROR < ABSENT
// * error == error (singleton type)
// * absent == absent (singleton type)
// * string compares on strings
// * numeric compares on numbers
// * false < true
// Exceptions for min & max:
// * absent-null always loses
// * empty-null always loses against numbers
static mv_binary_func_t* max_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING    INT       FLOAT     BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro,    _erro,    _erro,    _erro},
	/*ABSENT*/ {_erro, _absn, _2___, _2f__,    _2___,    _2___,    _2___},
	/*EMPTY*/  {_erro, _1___, _void, _2f__,    _2___,    _2___,    _2___},
	/*STRING*/ {_erro, _1f__, _1f__, max_s_ss, _1f__,    _1f__,    _1f__},
	/*INT*/    {_erro, _1___, _1___, _2f__,    max_i_ii, max_f_if, _2___},
	/*FLOAT*/  {_erro, _1___, _1___, _2f__,    max_f_fi, max_f_ff, _2___},
	/*BOOL*/   {_erro, _1___, _1___, _2f__,    _1___,    _1___,    max_b_bb},
	};

mv_t x_xx_max_func(mv_t* pval1, mv_t* pval2) {
	return (max_dispositions[pval1->type][pval2->type])(pval1,pval2);
}

mv_t variadic_max_func(mv_t* pvals, int nvals) {
	if (nvals == 0) {
		return mv_empty();
	} else {
		mv_t rv = pvals[0];
		for (int i = 1; i < nvals; i++) {
			rv = x_xx_max_func(&rv, &pvals[i]);
			// Disposition-matrix entries for max all free their non-return arguments
		}
		return rv;
	}
}

// ----------------------------------------------------------------
static mv_t int_i_b(mv_t* pa) { return mv_from_int(pa->u.boolv ? 1 : 0); }
static mv_t int_i_f(mv_t* pa) { return mv_from_int((long long)round(pa->u.fltv)); }
static mv_t int_i_i(mv_t* pa) { return mv_from_int(pa->u.intv); }
static mv_t int_i_s(mv_t* pa) {
	if (*pa->u.strv == '\0') {
		mv_free(pa);
		return mv_empty();
	}
	mv_t retval = mv_from_int(0LL);
	if (!mlr_try_int_from_string(pa->u.strv, &retval.u.intv)) {
		mv_free(pa);
		return mv_error();
	}
	mv_free(pa);
	return retval;
}

static mv_unary_func_t* int_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ int_i_s,
	/*INT*/    int_i_i,
	/*FLOAT*/  int_i_f,
	/*BOOL*/   int_i_b,
};

mv_t i_x_int_func(mv_t* pval1) { return (int_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t float_f_b(mv_t* pa) { return mv_from_float(pa->u.boolv ? 1.0 : 0.0); }
static mv_t float_f_f(mv_t* pa) { return mv_from_float(pa->u.fltv); }
static mv_t float_f_i(mv_t* pa) { return mv_from_float((double)pa->u.intv); }
static mv_t float_f_s(mv_t* pa) {
	if (*pa->u.strv == '\0') {
		mv_free(pa);
		return mv_empty();
	}
	mv_t retval = mv_from_float(0.0);
	if (!mlr_try_float_from_string(pa->u.strv, &retval.u.fltv)) {
		mv_free(pa);
		return mv_error();
	}
	mv_free(pa);
	return retval;
}

static mv_unary_func_t* float_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ float_f_s,
	/*INT*/    float_f_i,
	/*FLOAT*/  float_f_f,
	/*BOOL*/   float_f_b,
};

mv_t f_x_float_func(mv_t* pval1) { return (float_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t band_i_ii(mv_t* pa, mv_t* pb) {
	return mv_from_int(pa->u.intv & pb->u.intv);
}

static mv_binary_func_t* band_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT        FLOAT  BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,     _erro, _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,     _erro, _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,     _void, _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,     _erro, _erro},
	/*INT*/    {_erro, _1___, _void, _erro, band_i_ii, _erro, _erro},
	/*FLOAT*/  {_erro, _erro, _void, _erro, _erro,     _erro, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,     _erro, _erro},
	};

mv_t x_xx_band_func(mv_t* pval1, mv_t* pval2) { return (band_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t bor_i_ii(mv_t* pa, mv_t* pb) {
	return mv_from_int(pa->u.intv | pb->u.intv);
}

static mv_binary_func_t* bor_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT       FLOAT  BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,    _erro, _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,    _erro, _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,    _void, _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,    _erro, _erro},
	/*INT*/    {_erro, _1___, _void, _erro, bor_i_ii, _erro, _erro},
	/*FLOAT*/  {_erro, _erro, _void, _erro, _erro,    _erro, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,    _erro, _erro},
	};

mv_t x_xx_bor_func(mv_t* pval1, mv_t* pval2) { return (bor_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t bxor_i_ii(mv_t* pa, mv_t* pb) {
	return mv_from_int(pa->u.intv ^ pb->u.intv);
}

static mv_binary_func_t* bxor_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY  STRING INT        FLOAT  BOOL
	/*ERROR*/  {_erro, _erro, _erro, _erro, _erro,     _erro, _erro},
	/*ABSENT*/ {_erro, _absn, _absn, _erro, _2___,     _erro, _erro},
	/*EMPTY*/  {_erro, _absn, _void, _erro, _void,     _void, _erro},
	/*STRING*/ {_erro, _erro, _erro, _erro, _erro,     _erro, _erro},
	/*INT*/    {_erro, _1___, _void, _erro, bxor_i_ii, _erro, _erro},
	/*FLOAT*/  {_erro, _erro, _void, _erro, _erro,     _erro, _erro},
	/*BOOL*/   {_erro, _erro, _erro, _erro, _erro,     _erro, _erro},
	};

mv_t x_xx_bxor_func(mv_t* pval1, mv_t* pval2) { return (bxor_dispositions[pval1->type][pval2->type])(pval1,pval2); }

// ----------------------------------------------------------------
static mv_t boolean_b_b(mv_t* pa) { return mv_from_bool(pa->u.boolv); }
static mv_t boolean_b_f(mv_t* pa) { return mv_from_bool((pa->u.fltv == 0.0) ? FALSE : TRUE); }
static mv_t boolean_b_i(mv_t* pa) { return mv_from_bool((pa->u.intv == 0LL) ? FALSE : TRUE); }
static mv_t boolean_b_s(mv_t* pa) { return mv_from_bool((streq(pa->u.strv, "true") || streq(pa->u.strv, "TRUE")) ? TRUE : FALSE);}

static mv_unary_func_t* boolean_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ boolean_b_s,
	/*INT*/    boolean_b_i,
	/*FLOAT*/  boolean_b_f,
	/*BOOL*/   boolean_b_b,
};

mv_t b_x_boolean_func(mv_t* pval1) { return (boolean_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t string_s_b(mv_t* pa) { return mv_from_string_no_free(pa->u.boolv?"true":"false"); }
static mv_t string_s_f(mv_t* pa) { return mv_from_string_with_free(mlr_alloc_string_from_double(pa->u.fltv, MLR_GLOBALS.ofmt)); }
static mv_t string_s_i(mv_t* pa) { return mv_from_string_with_free(mlr_alloc_string_from_ll(pa->u.intv)); }
static mv_t string_s_s(mv_t* pa) {
	char free_flags = pa->free_flags;
	pa->free_flags = NO_FREE;
	return mv_from_string(pa->u.strv, free_flags);
}

static mv_unary_func_t* string_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ string_s_s,
	/*INT*/    string_s_i,
	/*FLOAT*/  string_s_f,
	/*BOOL*/   string_s_b,
};

mv_t s_x_string_func(mv_t* pval1) { return (string_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
mv_t s_sii_substr_func(mv_t* pval1, mv_t* pval2, mv_t* pval3) {
	int m = pval2->u.intv; // inclusive lower; -len..-1 alias to 0..len-1
	int n = pval3->u.intv; // inclusive upper; -len..-1 alias to 0..len-1
	int len = strlen(pval1->u.strv);
	mv_t rv;

	if (m < 0)
		m = len + m;
	if (n < 0)
		n = len + n;

	if (m < 0 || m >= len || n < 0 || n >= len || n < m) {
		rv = mv_from_string("", 0);
	} else {
		int olen = n - m + 1;
		char* p = mlr_malloc_or_die(olen + 1);
		strncpy(p, &pval1->u.strv[m], olen);
		p[olen] = 0;
		rv = mv_from_string(p, FREE_ENTRY_VALUE);
	}
	mv_free(pval1);
	mv_free(pval2);
	mv_free(pval3);
	return rv;
}

// ----------------------------------------------------------------
static mv_t hexfmt_s_b(mv_t* pa) { return mv_from_string_no_free(pa->u.boolv?"0x1":"0x0"); }
static mv_t hexfmt_s_f(mv_t* pa) { return mv_from_string_with_free(mlr_alloc_hexfmt_from_ll((long long)pa->u.fltv)); }
static mv_t hexfmt_s_i(mv_t* pa) { return mv_from_string_with_free(mlr_alloc_hexfmt_from_ll(pa->u.intv)); }
static mv_t hexfmt_s_s(mv_t* pa) {
	char free_flags = pa->free_flags;
	pa->free_flags = NO_FREE;
	return mv_from_string(pa->u.strv, free_flags);
}

static mv_unary_func_t* hexfmt_dispositions[MT_DIM] = {
	/*ERROR*/  _err1,
	/*ABSENT*/ _absn1,
	/*EMPTY*/  _emt1,
	/*STRING*/ hexfmt_s_s,
	/*INT*/    hexfmt_s_i,
	/*FLOAT*/  hexfmt_s_f,
	/*BOOL*/   hexfmt_s_b,
};

mv_t s_x_hexfmt_func(mv_t* pval1) { return (hexfmt_dispositions[pval1->type])(pval1); }

// ----------------------------------------------------------------
static mv_t fmtnum_s_bs(mv_t* pa, mv_t* pfmt) { return mv_from_string_no_free(pa->u.boolv?"0x1":"0x0"); }
static mv_t fmtnum_s_ds(mv_t* pa, mv_t* pfmt) {
	mv_t rv = mv_from_string_with_free(mlr_alloc_string_from_double(pa->u.fltv, pfmt->u.strv));
	mv_free(pfmt);
	return rv;
}
static mv_t fmtnum_s_is(mv_t* pa, mv_t* pfmt) {
	mv_t rv = mv_from_string_with_free(mlr_alloc_string_from_ll_and_format(pa->u.intv, pfmt->u.strv));
	mv_free(pfmt);
	return rv;
}
static mv_binary_func_t* fmtnum_dispositions[MT_DIM] = {
	/*ERROR*/  _erro,
	/*ABSENT*/ _absn,
	/*EMPTY*/  _void,
	/*STRING*/ _erro,
	/*INT*/    fmtnum_s_is,
	/*FLOAT*/  fmtnum_s_ds,
	/*BOOL*/   fmtnum_s_bs,
};

mv_t s_xs_fmtnum_func(mv_t* pval1, mv_t* pval2) { return (fmtnum_dispositions[pval1->type])(pval1, pval2); }

// ----------------------------------------------------------------
static mv_t eq_b_ii(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv == pb->u.intv); }
static mv_t ne_b_ii(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv != pb->u.intv); }
static mv_t gt_b_ii(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv >  pb->u.intv); }
static mv_t ge_b_ii(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv >= pb->u.intv); }
static mv_t lt_b_ii(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv <  pb->u.intv); }
static mv_t le_b_ii(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv <= pb->u.intv); }

static mv_t eq_b_ff(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv == pb->u.fltv); }
static mv_t ne_b_ff(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv != pb->u.fltv); }
static mv_t gt_b_ff(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv >  pb->u.fltv); }
static mv_t ge_b_ff(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv >= pb->u.fltv); }
static mv_t lt_b_ff(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv <  pb->u.fltv); }
static mv_t le_b_ff(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv <= pb->u.fltv); }

static mv_t eq_b_fi(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv == pb->u.intv); }
static mv_t ne_b_fi(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv != pb->u.intv); }
static mv_t gt_b_fi(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv >  pb->u.intv); }
static mv_t ge_b_fi(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv >= pb->u.intv); }
static mv_t lt_b_fi(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv <  pb->u.intv); }
static mv_t le_b_fi(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.fltv <= pb->u.intv); }

static mv_t eq_b_if(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv == pb->u.fltv); }
static mv_t ne_b_if(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv != pb->u.fltv); }
static mv_t gt_b_if(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv >  pb->u.fltv); }
static mv_t ge_b_if(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv >= pb->u.fltv); }
static mv_t lt_b_if(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv <  pb->u.fltv); }
static mv_t le_b_if(mv_t* pa, mv_t* pb) { return mv_from_bool(pa->u.intv <= pb->u.fltv); }

static mv_t eq_b_xs(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sa = mv_format_val(pa, &free_flags);
	mv_t rv = mv_from_bool(strcmp(sa, pb->u.strv) == 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sa);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t ne_b_xs(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sa = mv_format_val(pa, &free_flags);
	mv_t rv = mv_from_bool(strcmp(sa, pb->u.strv) != 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sa);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t gt_b_xs(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sa = mv_format_val(pa, &free_flags);
	mv_t rv = mv_from_bool(strcmp(sa, pb->u.strv) > 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sa);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t ge_b_xs(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sa = mv_format_val(pa, &free_flags);
	mv_t rv = mv_from_bool(strcmp(sa, pb->u.strv) >= 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sa);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t lt_b_xs(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sa = mv_format_val(pa, &free_flags);
	mv_t rv = mv_from_bool(strcmp(sa, pb->u.strv) < 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sa);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t le_b_xs(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sa = mv_format_val(pa, &free_flags);
	mv_t rv = mv_from_bool(strcmp(sa, pb->u.strv) <= 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sa);
	mv_free(pa);
	mv_free(pb);
	return rv;
}

static mv_t eq_b_sx(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sb = mv_format_val(pb, &free_flags);
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, sb) == 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sb);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t ne_b_sx(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sb = mv_format_val(pb, &free_flags);
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, sb) != 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sb);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t gt_b_sx(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sb = mv_format_val(pb, &free_flags);
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, sb) > 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sb);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t ge_b_sx(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sb = mv_format_val(pb, &free_flags);
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, sb) >= 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sb);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t lt_b_sx(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sb = mv_format_val(pb, &free_flags);
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, sb) < 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sb);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t le_b_sx(mv_t* pa, mv_t* pb) {
	char free_flags;
	char* sb = mv_format_val(pb, &free_flags);
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, sb) <= 0);
	if (free_flags & FREE_ENTRY_VALUE)
		free(sb);
	mv_free(pa);
	mv_free(pb);
	return rv;
}

static mv_t eq_b_ss(mv_t*pa, mv_t*pb) {
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, pb->u.strv) == 0);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t ne_b_ss(mv_t*pa, mv_t*pb) {
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, pb->u.strv) != 0);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t gt_b_ss(mv_t*pa, mv_t*pb) {
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, pb->u.strv) >  0);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t ge_b_ss(mv_t*pa, mv_t*pb) {
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, pb->u.strv) >= 0);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t lt_b_ss(mv_t*pa, mv_t*pb) {
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, pb->u.strv) <  0);
	mv_free(pa);
	mv_free(pb);
	return rv;
}
static mv_t le_b_ss(mv_t*pa, mv_t*pb) {
	mv_t rv = mv_from_bool(strcmp(pa->u.strv, pb->u.strv) <= 0);
	mv_free(pa);
	mv_free(pb);
	return rv;
}

static mv_binary_func_t* eq_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY    STRING   INT      FLOAT    BOOL
	/*ERROR*/  {_erro, _erro, _erro,   _erro,   _erro,   _erro,   _erro},
	/*ABSENT*/ {_erro, _absn, _absn,   _absn,   _absn,   _absn,   _absn},
	/*EMPTY*/  {_erro, _absn, eq_b_ss, eq_b_ss, eq_b_sx, eq_b_sx, _erro},
	/*STRING*/ {_erro, _absn, eq_b_ss, eq_b_ss, eq_b_sx, eq_b_sx, _erro},
	/*INT*/    {_erro, _absn, eq_b_xs, eq_b_xs, eq_b_ii, eq_b_if, _erro},
	/*FLOAT*/  {_erro, _absn, eq_b_xs, eq_b_xs, eq_b_fi, eq_b_ff, _erro},
	/*BOOL*/   {_erro, _erro, _absn,   _erro,   _erro,   _erro,   _erro},
	};

static mv_binary_func_t* ne_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY    STRING   INT      FLOAT    BOOL
	/*ERROR*/  {_erro, _erro, _erro,   _erro,   _erro,   _erro,   _erro},
	/*ABSENT*/ {_erro, _absn, _absn,   _absn,   _absn,   _absn,   _absn},
	/*EMPTY*/  {_erro, _absn, ne_b_ss, ne_b_ss, ne_b_sx, ne_b_sx, _erro},
	/*STRING*/ {_erro, _absn, ne_b_ss, ne_b_ss, ne_b_sx, ne_b_sx, _erro},
	/*INT*/    {_erro, _absn, ne_b_xs, ne_b_xs, ne_b_ii, ne_b_if, _erro},
	/*FLOAT*/  {_erro, _absn, ne_b_xs, ne_b_xs, ne_b_fi, ne_b_ff, _erro},
	/*BOOL*/   {_erro, _erro, _absn,   _erro,   _erro,   _erro,   _erro},
	};

static mv_binary_func_t* gt_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY    STRING   INT      FLOAT    BOOL
	/*ERROR*/  {_erro, _erro, _erro,   _erro,   _erro,   _erro,   _erro},
	/*ABSENT*/ {_erro, _absn, _absn,   _absn,   _absn,   _absn,   _absn},
	/*EMPTY*/  {_erro, _absn, gt_b_ss, gt_b_ss, gt_b_sx, gt_b_sx, _erro},
	/*STRING*/ {_erro, _absn, gt_b_ss, gt_b_ss, gt_b_sx, gt_b_sx, _erro},
	/*INT*/    {_erro, _absn, gt_b_xs, gt_b_xs, gt_b_ii, gt_b_if, _erro},
	/*FLOAT*/  {_erro, _absn, gt_b_xs, gt_b_xs, gt_b_fi, gt_b_ff, _erro},
	/*BOOL*/   {_erro, _erro, _absn,   _erro,   _erro,   _erro,   _erro},
	};

static mv_binary_func_t* ge_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY    STRING   INT      FLOAT    BOOL
	/*ERROR*/  {_erro, _erro, _erro,   _erro,   _erro,   _erro,   _erro},
	/*ABSENT*/ {_erro, _absn, _absn,   _absn,   _absn,   _absn,   _absn},
	/*EMPTY*/  {_erro, _absn, ge_b_ss, ge_b_ss, ge_b_sx, ge_b_sx, _erro},
	/*STRING*/ {_erro, _absn, ge_b_ss, ge_b_ss, ge_b_sx, ge_b_sx, _erro},
	/*INT*/    {_erro, _absn, ge_b_xs, ge_b_xs, ge_b_ii, ge_b_if, _erro},
	/*FLOAT*/  {_erro, _absn, ge_b_xs, ge_b_xs, ge_b_fi, ge_b_ff, _erro},
	/*BOOL*/   {_erro, _erro, _absn,   _erro,   _erro,   _erro,   _erro},
	};

static mv_binary_func_t* lt_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY    STRING   INT      FLOAT    BOOL
	/*ERROR*/  {_erro, _erro, _erro,   _erro,   _erro,   _erro,   _erro},
	/*ABSENT*/ {_erro, _absn, _absn,   _absn,   _absn,   _absn,   _absn},
	/*EMPTY*/  {_erro, _absn, lt_b_ss, lt_b_ss, lt_b_sx, lt_b_sx, _erro},
	/*STRING*/ {_erro, _absn, lt_b_ss, lt_b_ss, lt_b_sx, lt_b_sx, _erro},
	/*INT*/    {_erro, _absn, lt_b_xs, lt_b_xs, lt_b_ii, lt_b_if, _erro},
	/*FLOAT*/  {_erro, _absn, lt_b_xs, lt_b_xs, lt_b_fi, lt_b_ff, _erro},
	/*BOOL*/   {_erro, _erro, _absn,   _erro,   _erro,   _erro,   _erro},
	};

static mv_binary_func_t* le_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR   ABSENT EMPTY    STRING   INT      FLOAT    BOOL
	/*ERROR*/  {_erro, _erro, _erro,   _erro,   _erro,   _erro,   _erro},
	/*ABSENT*/ {_erro, _absn, _absn,   _absn,   _absn,   _absn,   _absn},
	/*EMPTY*/  {_erro, _absn, le_b_ss, le_b_ss, le_b_sx, le_b_sx, _erro},
	/*STRING*/ {_erro, _absn, le_b_ss, le_b_ss, le_b_sx, le_b_sx, _erro},
	/*INT*/    {_erro, _absn, le_b_xs, le_b_xs, le_b_ii, le_b_if, _erro},
	/*FLOAT*/  {_erro, _absn, le_b_xs, le_b_xs, le_b_fi, le_b_ff, _erro},
	/*BOOL*/   {_erro, _erro, _absn,   _erro,   _erro,   _erro,   _erro},
	};

mv_t eq_op_func(mv_t* pval1, mv_t* pval2) { return (eq_dispositions[pval1->type][pval2->type])(pval1, pval2); }
mv_t ne_op_func(mv_t* pval1, mv_t* pval2) { return (ne_dispositions[pval1->type][pval2->type])(pval1, pval2); }
mv_t gt_op_func(mv_t* pval1, mv_t* pval2) { return (gt_dispositions[pval1->type][pval2->type])(pval1, pval2); }
mv_t ge_op_func(mv_t* pval1, mv_t* pval2) { return (ge_dispositions[pval1->type][pval2->type])(pval1, pval2); }
mv_t lt_op_func(mv_t* pval1, mv_t* pval2) { return (lt_dispositions[pval1->type][pval2->type])(pval1, pval2); }
mv_t le_op_func(mv_t* pval1, mv_t* pval2) { return (le_dispositions[pval1->type][pval2->type])(pval1, pval2); }

// ----------------------------------------------------------------
int mv_equals_si(mv_t* pa, mv_t* pb) {
	if (pa->type == MT_INT) {
		return (pb->type == MT_INT) ? pa->u.intv == pb->u.intv : FALSE;
	} else {
		return (pb->type == MT_STRING) ? streq(pa->u.strv, pb->u.strv) : FALSE;
	}
}

// ----------------------------------------------------------------
static int eq_i_ii(mv_t* pa, mv_t* pb) { return  pa->u.intv == pb->u.intv; }
static int ne_i_ii(mv_t* pa, mv_t* pb) { return  pa->u.intv != pb->u.intv; }
static int gt_i_ii(mv_t* pa, mv_t* pb) { return  pa->u.intv >  pb->u.intv; }
static int ge_i_ii(mv_t* pa, mv_t* pb) { return  pa->u.intv >= pb->u.intv; }
static int lt_i_ii(mv_t* pa, mv_t* pb) { return  pa->u.intv <  pb->u.intv; }
static int le_i_ii(mv_t* pa, mv_t* pb) { return  pa->u.intv <= pb->u.intv; }

static int eq_i_ff(mv_t* pa, mv_t* pb) { return  pa->u.fltv == pb->u.fltv; }
static int ne_i_ff(mv_t* pa, mv_t* pb) { return  pa->u.fltv != pb->u.fltv; }
static int gt_i_ff(mv_t* pa, mv_t* pb) { return  pa->u.fltv >  pb->u.fltv; }
static int ge_i_ff(mv_t* pa, mv_t* pb) { return  pa->u.fltv >= pb->u.fltv; }
static int lt_i_ff(mv_t* pa, mv_t* pb) { return  pa->u.fltv <  pb->u.fltv; }
static int le_i_ff(mv_t* pa, mv_t* pb) { return  pa->u.fltv <= pb->u.fltv; }

static int eq_i_fi(mv_t* pa, mv_t* pb) { return  pa->u.fltv == pb->u.intv; }
static int ne_i_fi(mv_t* pa, mv_t* pb) { return  pa->u.fltv != pb->u.intv; }
static int gt_i_fi(mv_t* pa, mv_t* pb) { return  pa->u.fltv >  pb->u.intv; }
static int ge_i_fi(mv_t* pa, mv_t* pb) { return  pa->u.fltv >= pb->u.intv; }
static int lt_i_fi(mv_t* pa, mv_t* pb) { return  pa->u.fltv <  pb->u.intv; }
static int le_i_fi(mv_t* pa, mv_t* pb) { return  pa->u.fltv <= pb->u.intv; }

static int eq_i_if(mv_t* pa, mv_t* pb) { return  pa->u.intv == pb->u.fltv; }
static int ne_i_if(mv_t* pa, mv_t* pb) { return  pa->u.intv != pb->u.fltv; }
static int gt_i_if(mv_t* pa, mv_t* pb) { return  pa->u.intv >  pb->u.fltv; }
static int ge_i_if(mv_t* pa, mv_t* pb) { return  pa->u.intv >= pb->u.fltv; }
static int lt_i_if(mv_t* pa, mv_t* pb) { return  pa->u.intv <  pb->u.fltv; }
static int le_i_if(mv_t* pa, mv_t* pb) { return  pa->u.intv <= pb->u.fltv; }

static mv_i_nn_comparator_func_t* ieq_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT      FLOAT    BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  eq_i_ii, eq_i_if, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  eq_i_fi, eq_i_ff, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	};

static mv_i_nn_comparator_func_t* ine_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT      FLOAT    BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  ne_i_ii, ne_i_if, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  ne_i_fi, ne_i_ff, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	};

static mv_i_nn_comparator_func_t* igt_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT      FLOAT    BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  gt_i_ii, gt_i_if, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  gt_i_fi, gt_i_ff, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	};

static mv_i_nn_comparator_func_t* ige_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT      FLOAT    BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  ge_i_ii, ge_i_if, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  ge_i_fi, ge_i_ff, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	};

static mv_i_nn_comparator_func_t* ilt_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT      FLOAT    BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  lt_i_ii, lt_i_if, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  lt_i_fi, lt_i_ff, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	};

static mv_i_nn_comparator_func_t* ile_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT      FLOAT    BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  le_i_ii, le_i_if, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  le_i_fi, le_i_ff, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,    NULL,    NULL},
	};

int mv_i_nn_eq(mv_t* pval1, mv_t* pval2) { return (ieq_dispositions[pval1->type][pval2->type])(pval1, pval2); }
int mv_i_nn_ne(mv_t* pval1, mv_t* pval2) { return (ine_dispositions[pval1->type][pval2->type])(pval1, pval2); }
int mv_i_nn_gt(mv_t* pval1, mv_t* pval2) { return (igt_dispositions[pval1->type][pval2->type])(pval1, pval2); }
int mv_i_nn_ge(mv_t* pval1, mv_t* pval2) { return (ige_dispositions[pval1->type][pval2->type])(pval1, pval2); }
int mv_i_nn_lt(mv_t* pval1, mv_t* pval2) { return (ilt_dispositions[pval1->type][pval2->type])(pval1, pval2); }
int mv_i_nn_le(mv_t* pval1, mv_t* pval2) { return (ile_dispositions[pval1->type][pval2->type])(pval1, pval2); }

// ----------------------------------------------------------------
// For unit-test keystroke-saving

int mveq(mv_t* pval1, mv_t* pval2) {
	mv_t cmp = eq_op_func(pval1, pval2);
	MLR_INTERNAL_CODING_ERROR_UNLESS(cmp.type == MT_BOOLEAN);
	return cmp.u.boolv;
}

int mvne(mv_t* pval1, mv_t* pval2) {
	return !mveq(pval1, pval2);
}

int mveqcopy(mv_t* pval1, mv_t* pval2) {
	mv_t c1 = mv_copy(pval1);
	mv_t c2 = mv_copy(pval2);
	return mveq(&c1, &c2);
}

int mvnecopy(mv_t* pval1, mv_t* pval2) {
	mv_t c1 = mv_copy(pval1);
	mv_t c2 = mv_copy(pval2);
	return mvne(&c1, &c2);
}

// ----------------------------------------------------------------
// arg2 evaluates to string via compound expression; regexes compiled on each call.
mv_t matches_no_precomp_func(mv_t* pval1, mv_t* pval2, string_array_t** ppregex_captures) {
	char* s1 = pval1->u.strv;
	char* s2 = pval2->u.strv;

	regex_t regex;
	char* sstr   = s1;
	char* sregex = s2;

	regcomp_or_die(&regex, sregex, REG_NOSUB);

	const size_t nmatchmax = 10; // Capture-groups \1 through \9 supported, along with entire-string match
	regmatch_t matches[nmatchmax];
	if (regmatch_or_die(&regex, sstr, nmatchmax, matches)) {
		if (ppregex_captures != NULL && *ppregex_captures != NULL)
			save_regex_captures(ppregex_captures, pval1->u.strv, matches, nmatchmax);
		regfree(&regex);
		mv_free(pval1);
		mv_free(pval2);
		return mv_from_true();
	} else {
		regfree(&regex);
		mv_free(pval1);
		mv_free(pval2);
		return mv_from_false();
	}
}

mv_t does_not_match_no_precomp_func(mv_t* pval1, mv_t* pval2, string_array_t** ppregex_captures) {
	mv_t rv = matches_no_precomp_func(pval1, pval2, ppregex_captures);
	rv.u.boolv = !rv.u.boolv;
	return rv;
}

// ----------------------------------------------------------------
// arg2 is a string, compiled to regex only once at alloc time
mv_t matches_precomp_func(mv_t* pval1, regex_t* pregex, string_builder_t* psb, string_array_t** ppregex_captures) {
	const size_t nmatchmax = 10; // Capture-groups \1 through \9 supported, along with entire-string match
	regmatch_t matches[nmatchmax];
	if (regmatch_or_die(pregex, pval1->u.strv, nmatchmax, matches)) {
		if (ppregex_captures != NULL)
			save_regex_captures(ppregex_captures, pval1->u.strv, matches, nmatchmax);
		mv_free(pval1);
		return mv_from_true();
	} else {
		// See comments in mapper_put.c. Setting this array to length 0 (i.e. zero matches) signals to the
		// lrec-evaluator's from-literal function that we *are* in a regex-match context but there are *no* matches to
		// be interpolated.
		if (ppregex_captures != NULL) {
			if (*ppregex_captures != NULL)
				string_array_realloc(*ppregex_captures, 0);
			else
				*ppregex_captures = string_array_alloc(0);
		}
		mv_free(pval1);
		return mv_from_false();
	}
}

mv_t does_not_match_precomp_func(mv_t* pval1, regex_t* pregex, string_builder_t* psb, string_array_t** ppregex_captures) {
	mv_t rv = matches_precomp_func(pval1, pregex, psb, ppregex_captures);
	rv.u.boolv = !rv.u.boolv;
	return rv;
}

// ----------------------------------------------------------------
static int mv_ff_cmp(const mv_t* pa, const mv_t* pb) {
	double d = pa->u.fltv - pb->u.fltv;
	return (d < 0) ? -1 : (d > 0) ? 1 : 0;
}
static int mv_fi_cmp(const mv_t* pa, const mv_t* pb) {
	double d = pa->u.fltv - pb->u.intv;
	return (d < 0) ? -1 : (d > 0) ? 1 : 0;
}
static int mv_if_cmp(const mv_t* pa, const mv_t* pb) {
	double d = pa->u.intv - pb->u.fltv;
	return (d < 0) ? -1 : (d > 0) ? 1 : 0;
}
static int mv_ii_cmp(const mv_t* pa, const mv_t* pb) {
	long long d = pa->u.intv - pb->u.intv;
	return (d < 0) ? -1 : (d > 0) ? 1 : 0;
}
// We assume mv_t's coming into percentile keeper are int or double -- in particular, non-null.
static mv_i_xx_comparator_func_t* mv_nn_comparator_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR  ABSENT EMPTY STRING INT        FLOAT      BOOL
	/*ERROR*/  {NULL, NULL,  NULL, NULL,  NULL,      NULL,      NULL},
	/*ABSENT*/ {NULL, NULL,  NULL, NULL,  NULL,      NULL,      NULL},
	/*EMPTY*/  {NULL, NULL,  NULL, NULL,  NULL,      NULL,      NULL},
	/*STRING*/ {NULL, NULL,  NULL, NULL,  NULL,      NULL,      NULL},
	/*INT*/    {NULL, NULL,  NULL, NULL,  mv_ii_cmp, mv_if_cmp, NULL},
	/*FLOAT*/  {NULL, NULL,  NULL, NULL,  mv_fi_cmp, mv_ff_cmp, NULL},
	/*BOOL*/   {NULL, NULL,  NULL, NULL,  NULL,      NULL,      NULL},
	};

int mv_nn_comparator(const void* pva, const void* pvb) {
	const mv_t* pa = pva;
	const mv_t* pb = pvb;
	return mv_nn_comparator_dispositions[pa->type][pb->type](pa, pb);
}

// ----------------------------------------------------------------
// For general qsorting of mv_t's

static int mv_cmp_eq(const mv_t* pa, const mv_t* pb) { return  0; }
static int mv_cmp_lt(const mv_t* pa, const mv_t* pb) { return -1; }
static int mv_cmp_gt(const mv_t* pa, const mv_t* pb) { return  1; }

static int mv_bb_comparator(const mv_t* pa, const mv_t* pb) {
	int d = pa->u.boolv - pb->u.boolv;
	return (d < 0) ? -1 : (d > 0) ? 1 : 0;
}
static int mv_ss_cmp(const mv_t* pa, const mv_t* pb) {
	return strcmp(pa->u.strv, pb->u.strv);
}

// Sort rules (same for min, max, and comparator):
// * NUMERICS < BOOL < STRINGS < ERROR < ABSENT
// * error == error (singleton type)
// * absent == absent (singleton type)
// * string compares on strings
// * numeric compares on numbers
// * false < true
static mv_i_xx_comparator_func_t* mv_xx_comparator_dispositions[MT_DIM][MT_DIM] = {
	//         ERROR       ABSENT     EMPTY      STRING     INT        FLOAT      BOOL
	/*ERROR*/  {mv_cmp_eq, mv_cmp_lt, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt},
	/*ABSENT*/ {mv_cmp_gt, mv_cmp_eq, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt},
	/*EMPTY*/  {mv_cmp_lt, mv_cmp_lt, mv_cmp_eq, mv_ss_cmp, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt},
	/*STRING*/ {mv_cmp_lt, mv_cmp_lt, mv_ss_cmp, mv_ss_cmp, mv_cmp_gt, mv_cmp_gt, mv_cmp_gt},
	/*INT*/    {mv_cmp_lt, mv_cmp_lt, mv_cmp_lt, mv_cmp_lt, mv_ii_cmp, mv_if_cmp, mv_cmp_lt},
	/*FLOAT*/  {mv_cmp_lt, mv_cmp_lt, mv_cmp_lt, mv_cmp_lt, mv_fi_cmp, mv_ff_cmp, mv_cmp_lt},
	/*BOOL*/   {mv_cmp_lt, mv_cmp_lt, mv_cmp_lt, mv_cmp_lt, mv_cmp_gt, mv_cmp_gt, mv_bb_comparator},
	};

int mv_xx_comparator(const void* pva, const void* pvb) {
	const mv_t* pa = pva;
	const mv_t* pb = pvb;
	return mv_xx_comparator_dispositions[pa->type][pb->type](pa, pb);
}

// ----------------------------------------------------------------
int mlr_bsearch_mv_n_for_insert(mv_t* array, int size, mv_t* pvalue) {
	int lo = 0;
	int hi = size-1;
	int mid = (hi+lo)/2;
	int newmid;

	if (size == 0)
		return 0;
	if (mv_i_nn_gt(pvalue, &array[0]))
		return 0;
	if (mv_i_nn_lt(pvalue, &array[hi]))
		return size;

	while (lo < hi) {
		mv_t* pa = &array[mid];
		if (mv_i_nn_eq(pvalue, pa)) {
			return mid;
		}
		else if (mv_i_nn_gt(pvalue, pa)) {
			hi = mid;
			newmid = (hi+lo)/2;
		}
		else {
			lo = mid;
			newmid = (hi+lo)/2;
		}
		if (mid == newmid) {
			if (mv_i_nn_ge(pvalue, &array[lo]))
				return lo;
			else if (mv_i_nn_ge(pvalue, &array[hi]))
				return hi;
			else
				return hi+1;
		}
		mid = newmid;
	}

	return lo;
}
