tinymux/mux/lib/stringutil.cpp

7713 lines
293 KiB
C++
Raw Permalink Normal View History

/*! \file stringutil.cpp
* \brief String utility functions.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "core.h"
extern "C" {
#include "color_ops.h"
}
#include <math.h>
bool g_no_flash = false;
bool g_space_compress = true;
UTF8 *DCL_CDECL tprintf(const UTF8 *fmt,...)
{
static UTF8 buff[LBUF_SIZE];
va_list ap;
va_start(ap, fmt);
mux_vsnprintf(buff, LBUF_SIZE, fmt, ap);
va_end(ap);
return buff;
}
void DCL_CDECL safe_tprintf_str(UTF8 *str, UTF8 **bp, const UTF8 *fmt,...)
{
va_list ap;
va_start(ap, fmt);
size_t nAvailable = LBUF_SIZE - (*bp - str);
size_t len = mux_vsnprintf(*bp, static_cast<int>(nAvailable), fmt, ap);
va_end(ap);
*bp += len;
}
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
using namespace std;
const bool mux_isprint_ascii[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_isprint_cp437[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, // 0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 7
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
};
const bool mux_isprint_latin1[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 7
0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, // 8
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, // 9
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
};
const bool mux_isprint_latin2[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
};
const bool mux_isdigit[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_isxdigit[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 3
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_isazAZ[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_isalnum[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 3
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_isupper_ascii[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_islower_ascii[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool mux_isspace[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
// Characters which should be escaped for the secure()
// function: '%$\[](){},;'.
//
const bool mux_issecure[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
// Characters which should be escaped for the escape()
// function: '%\[]{};,()^$'.
//
const bool mux_isescape[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const bool ANSI_TokenTerminatorTable[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const unsigned char mux_hex2dec[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // 3
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const unsigned char mux_toupper_ascii[UCHAR_MAX+1] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 0
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // 1
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // 2
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // 3
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // 4
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, // 5
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // 6
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, // 7
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, // 8
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, // 9
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, // A
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, // B
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, // C
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, // D
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, // E
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF // F
};
const unsigned char mux_tolower_ascii[UCHAR_MAX+1] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 0
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // 1
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // 2
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // 3
0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // 4
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, // 5
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // 6
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, // 7
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, // 8
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, // 9
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, // A
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, // B
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, // C
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, // D
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, // E
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF // F
};
// This will help decode UTF-8 sequences.
//
// 0xxxxxxx ==> 00000000-01111111 ==> 00-7F 1 byte sequence.
// 10xxxxxx ==> 10000000-10111111 ==> 80-BF continue
// 110xxxxx ==> 11000000-11011111 ==> C0-DF 2 byte sequence.
// 1110xxxx ==> 11100000-11101111 ==> E0-EF 3 byte sequence.
// 11110xxx ==> 11110000-11110111 ==> F0-F7 4 byte sequence.
// 11111000-11111111 illegal
//
// Also, RFC 3629 specifies that 0xC0, 0xC1, and 0xF5-0xFF never
// appear in a valid sequence.
//
// The first byte gives the length of a sequence (UTF8_SIZE1 - UTF8_SIZE4).
// Bytes in the middle of a sequence map to UTF8_CONTINUE. Bytes which should
// not appear map to UTF8_ILLEGAL.
//
extern "C" const unsigned char utf8_FirstByte[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 8
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 9
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // A
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // B
6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E
4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // F
};
// Advance within a NUL-terminated UTF-8 string without stepping past '\0'.
// Invalid lead bytes are treated as single-byte units.
static size_t utf8_advance_nul(const UTF8 *p)
{
if (nullptr == p || '\0' == *p)
{
return 0;
}
size_t n = utf8_FirstByte[static_cast<unsigned char>(*p)];
if (n >= UTF8_CONTINUE)
{
n = UTF8_SIZE1;
}
for (size_t i = 1; i < n; i++)
{
if ('\0' == p[i] || UTF8_CONTINUE != utf8_FirstByte[p[i]])
{
return UTF8_SIZE1;
}
}
UTF32 cp = utf8_decode_raw(p, n);
if (!utf8_is_valid_scalar(cp, n))
{
return UTF8_SIZE1;
}
return n;
}
// Advance within a UTF-8 byte span with explicit remaining length.
// Invalid/truncated/illegal scalars are treated as single-byte units.
static size_t utf8_advance_bounded(const UTF8 *p, size_t nAvail)
{
if (nullptr == p || 0 == nAvail)
{
return 0;
}
size_t n = utf8_FirstByte[static_cast<unsigned char>(*p)];
if (n < 1 || n >= UTF8_CONTINUE || n > nAvail)
{
return UTF8_SIZE1;
}
for (size_t i = 1; i < n; i++)
{
if (UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(p[i])])
{
return UTF8_SIZE1;
}
}
UTF32 cp = utf8_decode_raw(p, n);
if (!utf8_is_valid_scalar(cp, n))
{
return UTF8_SIZE1;
}
return n;
}
// The following table maps cp437 characters to their corresponding
// UTF8 sequences.
//
const UTF8 *cp437_utf8[256] =
{
T("\xE2\x80\x87"), T("\xE2\x98\xBA"), T("\xE2\x98\xBB"), T("\xE2\x99\xA5"),
T("\xE2\x99\xA6"), T("\xE2\x99\xA3"), T("\xE2\x99\xA0"), T("\xE2\x80\xA2"),
T("\xE2\x97\x98"), T("\xE2\x97\x8B"), T("\xE2\x97\x99"), T("\xE2\x99\x82"),
T("\xE2\x99\x80"), T("\xE2\x99\xAA"), T("\xE2\x99\xAB"), T("\xE2\x98\xBC"),
T("\xE2\x96\xBA"), T("\xE2\x97\x84"), T("\xE2\x86\x95"), T("\xE2\x80\xBC"),
T("\xC2\xB6"), T("\xC2\xA7"), T("\xE2\x96\xAC"), T("\xE2\x86\xA8"),
T("\xE2\x86\x91"), T("\xE2\x86\x93"), T("\xE2\x86\x92"), T("\xE2\x86\x90"),
T("\xE2\x88\x9F"), T("\xE2\x86\x94"), T("\xE2\x96\xB2"), T("\xE2\x96\xBC"),
T("\x20"), T("\x21"), T("\x22"), T("\x23"),
T("\x24"), T("\x25"), T("\x26"), T("\x27"),
T("\x28"), T("\x29"), T("\x2A"), T("\x2B"),
T("\x2C"), T("\x2D"), T("\x2E"), T("\x2F"),
T("\x30"), T("\x31"), T("\x32"), T("\x33"),
T("\x34"), T("\x35"), T("\x36"), T("\x37"),
T("\x38"), T("\x39"), T("\x3A"), T("\x3B"),
T("\x3C"), T("\x3D"), T("\x3E"), T("\x3F"),
T("\x40"), T("\x41"), T("\x42"), T("\x43"),
T("\x44"), T("\x45"), T("\x46"), T("\x47"),
T("\x48"), T("\x49"), T("\x4A"), T("\x4B"),
T("\x4C"), T("\x4D"), T("\x4E"), T("\x4F"),
T("\x50"), T("\x51"), T("\x52"), T("\x53"),
T("\x54"), T("\x55"), T("\x56"), T("\x57"),
T("\x58"), T("\x59"), T("\x5A"), T("\x5B"),
T("\x5C"), T("\x5D"), T("\x5E"), T("\x5F"),
T("\x60"), T("\x61"), T("\x62"), T("\x63"),
T("\x64"), T("\x65"), T("\x66"), T("\x67"),
T("\x68"), T("\x69"), T("\x6A"), T("\x6B"),
T("\x6C"), T("\x6D"), T("\x6E"), T("\x6F"),
T("\x70"), T("\x71"), T("\x72"), T("\x73"),
T("\x74"), T("\x75"), T("\x76"), T("\x77"),
T("\x78"), T("\x79"), T("\x7A"), T("\x7B"),
T("\x7C"), T("\x7D"), T("\x7E"), T("\x7F"),
T("\xC3\x87"), T("\xC3\xBC"), T("\xC3\xA9"), T("\xC3\xA2"),
T("\xC3\xA4"), T("\xC3\xA0"), T("\xC3\xA5"), T("\xC3\xA7"),
T("\xC3\xAA"), T("\xC3\xAB"), T("\xC3\xA8"), T("\xC3\xAF"),
T("\xC3\xAE"), T("\xC3\xAC"), T("\xC3\x84"), T("\xC3\x85"),
T("\xC3\x89"), T("\xC3\xA6"), T("\xC3\x86"), T("\xC3\xB4"),
T("\xC3\xB6"), T("\xC3\xB2"), T("\xC3\xBB"), T("\xC3\xB9"),
T("\xC3\xBF"), T("\xC3\x96"), T("\xC3\x9C"), T("\xC2\xA2"),
T("\xC2\xA3"), T("\xC2\xA5"), T("\xE2\x82\xA7"), T("\xC6\x92"),
T("\xC3\xA1"), T("\xC3\xAD"), T("\xC3\xB3"), T("\xC3\xBA"),
T("\xC3\xB1"), T("\xC3\x91"), T("\xC2\xAA"), T("\xC2\xBA"),
T("\xC2\xBF"), T("\xE2\x8C\x90"), T("\xC2\xAC"), T("\xC2\xBD"),
T("\xC2\xBC"), T("\xC2\xA1"), T("\xC2\xAB"), T("\xC2\xBB"),
T("\xE2\x96\x91"), T("\xE2\x96\x92"), T("\xE2\x96\x93"), T("\xE2\x94\x82"),
T("\xE2\x94\xA4"), T("\xE2\x95\xA1"), T("\xE2\x95\xA2"), T("\xE2\x95\x96"),
T("\xE2\x95\x95"), T("\xE2\x95\xA3"), T("\xE2\x95\x91"), T("\xE2\x95\x97"),
T("\xE2\x95\x9D"), T("\xE2\x95\x9C"), T("\xE2\x95\x9B"), T("\xE2\x94\x90"),
T("\xE2\x94\x94"), T("\xE2\x94\xB4"), T("\xE2\x94\xAC"), T("\xE2\x94\x9C"),
T("\xE2\x94\x80"), T("\xE2\x94\xBC"), T("\xE2\x95\x9E"), T("\xE2\x95\x9F"),
T("\xE2\x95\x9A"), T("\xE2\x95\x94"), T("\xE2\x95\xA9"), T("\xE2\x95\xA6"),
T("\xE2\x95\xA0"), T("\xE2\x95\x90"), T("\xE2\x95\xAC"), T("\xE2\x95\xA7"),
T("\xE2\x95\xA8"), T("\xE2\x95\xA4"), T("\xE2\x95\xA5"), T("\xE2\x95\x99"),
T("\xE2\x95\x98"), T("\xE2\x95\x92"), T("\xE2\x95\x93"), T("\xE2\x95\xAB"),
T("\xE2\x95\xAA"), T("\xE2\x94\x98"), T("\xE2\x94\x8C"), T("\xE2\x96\x88"),
T("\xE2\x96\x84"), T("\xE2\x96\x8C"), T("\xE2\x96\x90"), T("\xE2\x96\x80"),
T("\xCE\xB1"), T("\xC3\x9F"), T("\xCE\x93"), T("\xCF\x80"),
T("\xCE\xA3"), T("\xCF\x83"), T("\xC2\xB5"), T("\xCF\x84"),
T("\xCE\xA6"), T("\xCE\x98"), T("\xCE\xA9"), T("\xCE\xB4"),
T("\xE2\x88\x9E"), T("\xCF\x86"), T("\xCE\xB5"), T("\xE2\x88\xA9"),
T("\xE2\x89\xA1"), T("\xC2\xB1"), T("\xE2\x89\xA5"), T("\xE2\x89\xA4"),
T("\xE2\x8C\xA0"), T("\xE2\x8C\xA1"), T("\xC3\xB7"), T("\xE2\x89\x88"),
T("\xC2\xB0"), T("\xE2\x88\x99"), T("\xC2\xB7"), T("\xE2\x88\x9A"),
T("\xE2\x81\xBF"), T("\xC2\xB2"), T("\xE2\x96\xA0"), T("\xC2\xA0"),
};
// The following table maps existing 8-bit characters to their corresponding
// UTF8 sequences.
//
const UTF8 *latin1_utf8[256] =
{
T(""), T("\x01"), T("\x02"), T("\x03"),
T("\x04"), T("\x05"), T("\x06"), T("\x07"),
T("\x08"), T("\x09"), T("\x0A"), T("\x0B"),
T("\x0C"), T("\x0D"), T("\x0E"), T("\x0F"),
T("\x10"), T("\x11"), T("\x12"), T("\x13"),
T("\x14"), T("\x15"), T("\x16"), T("\x17"),
T("\x18"), T("\x19"), T("\x1A"), T("\x1B"),
T("\x1C"), T("\x1D"), T("\x1E"), T("\x1F"),
T("\x20"), T("\x21"), T("\x22"), T("\x23"),
T("\x24"), T("\x25"), T("\x26"), T("\x27"),
T("\x28"), T("\x29"), T("\x2A"), T("\x2B"),
T("\x2C"), T("\x2D"), T("\x2E"), T("\x2F"),
T("\x30"), T("\x31"), T("\x32"), T("\x33"),
T("\x34"), T("\x35"), T("\x36"), T("\x37"),
T("\x38"), T("\x39"), T("\x3A"), T("\x3B"),
T("\x3C"), T("\x3D"), T("\x3E"), T("\x3F"),
T("\x40"), T("\x41"), T("\x42"), T("\x43"),
T("\x44"), T("\x45"), T("\x46"), T("\x47"),
T("\x48"), T("\x49"), T("\x4A"), T("\x4B"),
T("\x4C"), T("\x4D"), T("\x4E"), T("\x4F"),
T("\x50"), T("\x51"), T("\x52"), T("\x53"),
T("\x54"), T("\x55"), T("\x56"), T("\x57"),
T("\x58"), T("\x59"), T("\x5A"), T("\x5B"),
T("\x5C"), T("\x5D"), T("\x5E"), T("\x5F"),
T("\x60"), T("\x61"), T("\x62"), T("\x63"),
T("\x64"), T("\x65"), T("\x66"), T("\x67"),
T("\x68"), T("\x69"), T("\x6A"), T("\x6B"),
T("\x6C"), T("\x6D"), T("\x6E"), T("\x6F"),
T("\x70"), T("\x71"), T("\x72"), T("\x73"),
T("\x74"), T("\x75"), T("\x76"), T("\x77"),
T("\x78"), T("\x79"), T("\x7A"), T("\x7B"),
T("\x7C"), T("\x7D"), T("\x7E"), T("\x7F"),
T("\xE2\x82\xAC"), T("\xEF\xBF\xBD"), T("\xE2\x80\x9A"), T("\xC6\x92"),
T("\xE2\x80\x9E"), T("\xE2\x80\xA6"), T("\xE2\x80\xA0"), T("\xE2\x80\xA1"),
T("\xCB\x86"), T("\xE2\x80\xB0"), T("\xC5\xA0"), T("\xE2\x80\xB9"),
T("\xC5\x92"), T("\xEF\xBF\xBD"), T("\xC5\xBD"), T("\xEF\xBF\xBD"),
T("\xEF\xBF\xBD"), T("\xE2\x80\x98"), T("\xE2\x80\x99"), T("\xE2\x80\x9C"),
T("\xE2\x80\x9D"), T("\xE2\x80\xA2"), T("\xE2\x80\x93"), T("\xE2\x80\x94"),
T("\xCB\x9C"), T("\xE2\x84\xA2"), T("\xC5\xA1"), T("\xE2\x80\xBA"),
T("\xC5\x93"), T("\xEF\xBF\xBD"), T("\xC5\xBE"), T("\xC5\xB8"),
T("\xC2\xA0"), T("\xC2\xA1"), T("\xC2\xA2"), T("\xC2\xA3"),
T("\xC2\xA4"), T("\xC2\xA5"), T("\xC2\xA6"), T("\xC2\xA7"),
T("\xC2\xA8"), T("\xC2\xA9"), T("\xC2\xAA"), T("\xC2\xAB"),
T("\xC2\xAC"), T("\xC2\xAD"), T("\xC2\xAE"), T("\xC2\xAF"),
T("\xC2\xB0"), T("\xC2\xB1"), T("\xC2\xB2"), T("\xC2\xB3"),
T("\xC2\xB4"), T("\xC2\xB5"), T("\xC2\xB6"), T("\xC2\xB7"),
T("\xC2\xB8"), T("\xC2\xB9"), T("\xC2\xBA"), T("\xC2\xBB"),
T("\xC2\xBC"), T("\xC2\xBD"), T("\xC2\xBE"), T("\xC2\xBF"),
T("\xC3\x80"), T("\xC3\x81"), T("\xC3\x82"), T("\xC3\x83"),
T("\xC3\x84"), T("\xC3\x85"), T("\xC3\x86"), T("\xC3\x87"),
T("\xC3\x88"), T("\xC3\x89"), T("\xC3\x8A"), T("\xC3\x8B"),
T("\xC3\x8C"), T("\xC3\x8D"), T("\xC3\x8E"), T("\xC3\x8F"),
T("\xC3\x90"), T("\xC3\x91"), T("\xC3\x92"), T("\xC3\x93"),
T("\xC3\x94"), T("\xC3\x95"), T("\xC3\x96"), T("\xC3\x97"),
T("\xC3\x98"), T("\xC3\x99"), T("\xC3\x9A"), T("\xC3\x9B"),
T("\xC3\x9C"), T("\xC3\x9D"), T("\xC3\x9E"), T("\xC3\x9F"),
T("\xC3\xA0"), T("\xC3\xA1"), T("\xC3\xA2"), T("\xC3\xA3"),
T("\xC3\xA4"), T("\xC3\xA5"), T("\xC3\xA6"), T("\xC3\xA7"),
T("\xC3\xA8"), T("\xC3\xA9"), T("\xC3\xAA"), T("\xC3\xAB"),
T("\xC3\xAC"), T("\xC3\xAD"), T("\xC3\xAE"), T("\xC3\xAF"),
T("\xC3\xB0"), T("\xC3\xB1"), T("\xC3\xB2"), T("\xC3\xB3"),
T("\xC3\xB4"), T("\xC3\xB5"), T("\xC3\xB6"), T("\xC3\xB7"),
T("\xC3\xB8"), T("\xC3\xB9"), T("\xC3\xBA"), T("\xC3\xBB"),
T("\xC3\xBC"), T("\xC3\xBD"), T("\xC3\xBE"), T("\xC3\xBF"),
};
// The following table maps latin2 characters to their corresponding
// UTF8 sequences.
//
const UTF8 *latin2_utf8[256] =
{
T(""), T("\x01"), T("\x02"), T("\x03"),
T("\x04"), T("\x05"), T("\x06"), T("\x07"),
T("\x08"), T("\x09"), T("\x0A"), T("\x0B"),
T("\x0C"), T("\x0D"), T("\x0E"), T("\x0F"),
T("\x10"), T("\x11"), T("\x12"), T("\x13"),
T("\x14"), T("\x15"), T("\x16"), T("\x17"),
T("\x18"), T("\x19"), T("\x1A"), T("\x1B"),
T("\x1C"), T("\x1D"), T("\x1E"), T("\x1F"),
T("\x20"), T("\x21"), T("\x22"), T("\x23"),
T("\x24"), T("\x25"), T("\x26"), T("\x27"),
T("\x28"), T("\x29"), T("\x2A"), T("\x2B"),
T("\x2C"), T("\x2D"), T("\x2E"), T("\x2F"),
T("\x30"), T("\x31"), T("\x32"), T("\x33"),
T("\x34"), T("\x35"), T("\x36"), T("\x37"),
T("\x38"), T("\x39"), T("\x3A"), T("\x3B"),
T("\x3C"), T("\x3D"), T("\x3E"), T("\x3F"),
T("\x40"), T("\x41"), T("\x42"), T("\x43"),
T("\x44"), T("\x45"), T("\x46"), T("\x47"),
T("\x48"), T("\x49"), T("\x4A"), T("\x4B"),
T("\x4C"), T("\x4D"), T("\x4E"), T("\x4F"),
T("\x50"), T("\x51"), T("\x52"), T("\x53"),
T("\x54"), T("\x55"), T("\x56"), T("\x57"),
T("\x58"), T("\x59"), T("\x5A"), T("\x5B"),
T("\x5C"), T("\x5D"), T("\x5E"), T("\x5F"),
T("\x60"), T("\x61"), T("\x62"), T("\x63"),
T("\x64"), T("\x65"), T("\x66"), T("\x67"),
T("\x68"), T("\x69"), T("\x6A"), T("\x6B"),
T("\x6C"), T("\x6D"), T("\x6E"), T("\x6F"),
T("\x70"), T("\x71"), T("\x72"), T("\x73"),
T("\x74"), T("\x75"), T("\x76"), T("\x77"),
T("\x78"), T("\x79"), T("\x7A"), T("\x7B"),
T("\x7C"), T("\x7D"), T("\x7E"), T("\x7F"),
T("\xC2\x80"), T("\xC2\x81"), T("\xC2\x82"), T("\xC2\x83"),
T("\xC2\x84"), T("\xC2\x85"), T("\xC2\x86"), T("\xC2\x87"),
T("\xC2\x88"), T("\xC2\x89"), T("\xC2\x8A"), T("\xC2\x8B"),
T("\xC2\x8C"), T("\xC2\x8D"), T("\xC2\x8E"), T("\xC2\x8F"),
T("\xC2\x90"), T("\xC2\x91"), T("\xC2\x92"), T("\xC2\x93"),
T("\xC2\x94"), T("\xC2\x95"), T("\xC2\x96"), T("\xC2\x97"),
T("\xC2\x98"), T("\xC2\x99"), T("\xC2\x9A"), T("\xC2\x9B"),
T("\xC2\x9C"), T("\xC2\x9D"), T("\xC2\x9E"), T("\xC2\x9F"),
T("\xC2\xA0"), T("\xC4\x84"), T("\xCB\x98"), T("\xC5\x81"),
T("\xC2\xA4"), T("\xC4\xBD"), T("\xC5\x9A"), T("\xC2\xA7"),
T("\xC2\xA8"), T("\xC5\xA0"), T("\xC5\x9E"), T("\xC5\xA4"),
T("\xC5\xB9"), T("\xC2\xAD"), T("\xC5\xBD"), T("\xC5\xBB"),
T("\xC2\xB0"), T("\xC4\x85"), T("\xCB\x9B"), T("\xC5\x82"),
T("\xC2\xB4"), T("\xC4\xBE"), T("\xC5\x9B"), T("\xCB\x87"),
T("\xC2\xB8"), T("\xC5\xA1"), T("\xC5\x9F"), T("\xC5\xA5"),
T("\xC5\xBA"), T("\xCB\x9D"), T("\xC5\xBE"), T("\xC5\xBC"),
T("\xC5\x94"), T("\xC3\x81"), T("\xC3\x82"), T("\xC4\x82"),
T("\xC3\x84"), T("\xC4\xB9"), T("\xC4\x86"), T("\xC3\x87"),
T("\xC4\x8C"), T("\xC3\x89"), T("\xC4\x98"), T("\xC3\x8B"),
T("\xC4\x9A"), T("\xC3\x8D"), T("\xC3\x8E"), T("\xC4\x8E"),
T("\xC4\x90"), T("\xC5\x83"), T("\xC5\x87"), T("\xC3\x93"),
T("\xC3\x94"), T("\xC5\x90"), T("\xC3\x96"), T("\xC3\x97"),
T("\xC5\x98"), T("\xC5\xAE"), T("\xC3\x9A"), T("\xC5\xB0"),
T("\xC3\x9C"), T("\xC3\x9D"), T("\xC5\xA2"), T("\xC3\x9F"),
T("\xC5\x95"), T("\xC3\xA1"), T("\xC3\xA2"), T("\xC4\x83"),
T("\xC3\xA4"), T("\xC4\xBA"), T("\xC4\x87"), T("\xC3\xA7"),
T("\xC4\x8D"), T("\xC3\xA9"), T("\xC4\x99"), T("\xC3\xAB"),
T("\xC4\x9B"), T("\xC3\xAD"), T("\xC3\xAE"), T("\xC4\x8F"),
T("\xC4\x91"), T("\xC5\x84"), T("\xC5\x88"), T("\xC3\xB3"),
T("\xC3\xB4"), T("\xC5\x91"), T("\xC3\xB6"), T("\xC3\xB7"),
T("\xC5\x99"), T("\xC5\xAF"), T("\xC3\xBA"), T("\xC5\xB1"),
T("\xC3\xBC"), T("\xC3\xBD"), T("\xC5\xA3"), T("\xCB\x99"),
};
/*! \brief Validates UTF8 string and returns number of code points contained therein.
*
* \param pString UTF8 string.
* \param nString Resulting number of code points in pString.
* \return true for valid, false for invalid.
*/
2025-03-24 14:53:29 -06:00
bool utf8_strlen(const UTF8 *pString, size_t &nString)
{
nString = 0;
int i = 0;
while ('\0' != pString[i])
{
unsigned char t = utf8_FirstByte[pString[i]];
if (UTF8_CONTINUE <= t)
{
return false;
}
int j;
for (j = i + 1; j < i + t; j++)
{
if ( '\0' == pString[j]
|| UTF8_CONTINUE != utf8_FirstByte[pString[j]])
{
return false;
}
}
UTF32 cp = utf8_decode_raw(pString + i, t);
if (!utf8_is_valid_scalar(cp, t))
{
return false;
}
nString++;
i = i + t;
}
return true;
}
bool utf8_strlen(const UTF8 *pString, mux_cursor &nString)
{
LBUF_OFFSET nPoints = 0;
LBUF_OFFSET nBytes = 0;
while ('\0' != pString[nBytes])
{
unsigned char t = utf8_FirstByte[pString[nBytes]];
if (UTF8_CONTINUE <= t)
{
nString(nBytes, nPoints);
return false;
}
int j;
for (j = nBytes + 1; j < nBytes + t; j++)
{
if ( '\0' == pString[j]
|| UTF8_CONTINUE != utf8_FirstByte[pString[j]])
{
nString(nBytes, nPoints);
return false;
}
}
UTF32 cp = utf8_decode_raw(pString + nBytes, t);
if (!utf8_is_valid_scalar(cp, t))
{
nString(nBytes, nPoints);
return false;
}
nPoints++;
nBytes = nBytes + t;
}
nString(nBytes, nPoints);
return true;
}
/*! \brief Convert UTF8 to ASCII with '?' for all unsupported characters.
*
* \param pString UTF8 string.
* \return Equivalent string in ASCII codeset.
*/
2025-03-24 14:53:29 -06:00
const UTF8 *ConvertToAscii(const UTF8 *pString)
{
static UTF8 buffer[2*LBUF_SIZE];
UTF8 *q = buffer;
while ('\0' != *pString)
{
*q++ = static_cast<UTF8>(co_dfa_ascii(pString));
pString += utf8_advance_nul(pString);
}
*q = '\0';
return buffer;
}
/*! \brief Convert UTF8 to cp437 with '?' for all unsupported characters.
*
* \param pString UTF8 string.
* \return Equivalent string in latin1 codeset.
*/
2025-03-24 14:53:29 -06:00
const UTF8 *ConvertToCp437(const UTF8 *pString)
{
static UTF8 buffer[2*LBUF_SIZE];
UTF8 *q = buffer;
while ( '\0' != *pString
&& q < buffer + sizeof(buffer) - 1)
{
const UTF8 *p = pString;
int iState = TR_CP437_START_STATE;
do
{
UTF8 ch = *p++;
unsigned char iColumn = tr_cp437_itt[static_cast<unsigned char>(ch)];
unsigned short iOffset = tr_cp437_sot[iState];
for (;;)
{
int y = tr_cp437_sbt[iOffset];
if (y < 128)
{
// RUN phrase.
//
if (iColumn < y)
{
iState = tr_cp437_sbt[iOffset+1];
break;
}
else
{
iColumn = static_cast<unsigned char>(iColumn - y);
iOffset += 2;
}
}
else
{
// COPY phrase.
//
y = 256-y;
if (iColumn < y)
{
iState = tr_cp437_sbt[iOffset+iColumn+1];
break;
}
else
{
iColumn = static_cast<unsigned char>(iColumn - y);
iOffset = static_cast<unsigned short>(iOffset + y + 1);
}
}
}
} while (iState < TR_CP437_ACCEPTING_STATES_START);
*q++ = static_cast<char>(iState - TR_CP437_ACCEPTING_STATES_START);
pString += utf8_advance_nul(pString);
}
*q = '\0';
return buffer;
}
/*! \brief Convert UTF8 to latin1 with '?' for all unsupported characters.
*
* \param pString UTF8 string.
* \return Equivalent string in latin1 codeset.
*/
2025-03-24 14:53:29 -06:00
const UTF8 *ConvertToLatin1(const UTF8 *pString)
{
static UTF8 buffer[2*LBUF_SIZE];
UTF8 *q = buffer;
while ( '\0' != *pString
&& q < buffer + sizeof(buffer) - 1)
{
const UTF8 *p = pString;
int iState = TR_LATIN1_START_STATE;
do
{
UTF8 ch = *p++;
unsigned char iColumn = tr_latin1_itt[static_cast<unsigned char>(ch)];
2009-02-01 13:21:48 -08:00
unsigned short iOffset = tr_latin1_sot[iState];
for (;;)
{
int y = tr_latin1_sbt[iOffset];
if (y < 128)
2009-02-01 13:21:48 -08:00
{
// RUN phrase.
//
if (iColumn < y)
{
iState = tr_latin1_sbt[iOffset+1];
break;
}
else
{
2010-06-12 13:38:47 -07:00
iColumn = static_cast<unsigned char>(iColumn - y);
2009-02-01 13:21:48 -08:00
iOffset += 2;
}
}
else
{
// COPY phrase.
//
y = 256-y;
2009-02-01 13:21:48 -08:00
if (iColumn < y)
{
iState = tr_latin1_sbt[iOffset+iColumn+1];
break;
}
else
{
2010-06-12 13:38:47 -07:00
iColumn = static_cast<unsigned char>(iColumn - y);
iOffset = static_cast<unsigned short>(iOffset + y + 1);
2009-02-01 13:21:48 -08:00
}
}
}
} while (iState < TR_LATIN1_ACCEPTING_STATES_START);
*q++ = static_cast<char>(iState - TR_LATIN1_ACCEPTING_STATES_START);
pString += utf8_advance_nul(pString);
}
*q = '\0';
return buffer;
}
/*! \brief Convert UTF8 to latin2 with '?' for all unsupported characters.
*
* \param pString UTF8 string.
* \return Equivalent string in latin1 codeset.
*/
2025-03-24 14:53:29 -06:00
const UTF8 *ConvertToLatin2(const UTF8 *pString)
{
static UTF8 buffer[2*LBUF_SIZE];
UTF8 *q = buffer;
while ( '\0' != *pString
&& q < buffer + sizeof(buffer) - 1)
{
const UTF8 *p = pString;
int iState = TR_LATIN2_START_STATE;
do
{
UTF8 ch = *p++;
unsigned char iColumn = tr_latin2_itt[static_cast<unsigned char>(ch)];
unsigned short iOffset = tr_latin2_sot[iState];
2009-02-01 13:21:48 -08:00
for (;;)
{
int y = tr_latin2_sbt[iOffset];
if (y < 128)
2009-02-01 13:21:48 -08:00
{
// RUN phrase.
//
if (iColumn < y)
{
iState = tr_latin2_sbt[iOffset+1];
2009-02-01 13:21:48 -08:00
break;
}
else
{
2010-06-12 13:38:47 -07:00
iColumn = static_cast<unsigned char>(iColumn - y);
2009-02-01 13:21:48 -08:00
iOffset += 2;
}
}
else
{
// COPY phrase.
//
y = 256-y;
2009-02-01 13:21:48 -08:00
if (iColumn < y)
{
iState = tr_latin2_sbt[iOffset+iColumn+1];
2009-02-01 13:21:48 -08:00
break;
}
else
{
2010-06-12 13:38:47 -07:00
iColumn = static_cast<unsigned char>(iColumn - y);
iOffset = static_cast<unsigned short>(iOffset + y + 1);
2009-02-01 13:21:48 -08:00
}
}
}
} while (iState < TR_LATIN2_ACCEPTING_STATES_START);
*q++ = static_cast<char>(iState - TR_LATIN2_ACCEPTING_STATES_START);
pString += utf8_advance_nul(pString);
}
*q = '\0';
return buffer;
}
/*! \brief Return ConsoleWidth property for single code point.
*
* \param pCodePoint UTF8 string.
* \return Width to allow in formatting.
*/
2025-03-24 14:53:29 -06:00
int ConsoleWidth(const UTF8 *pCodePoint)
{
const UTF8 *p = pCodePoint;
int iState = TR_WIDTHS_START_STATE;
do
{
unsigned char ch = *p++;
unsigned char iColumn = tr_widths_itt[static_cast<unsigned char>(ch)];
unsigned short iOffset = tr_widths_sot[iState];
for (;;)
{
int y = tr_widths_sbt[iOffset];
if (y < 128)
{
// RUN phrase.
//
if (iColumn < y)
{
iState = tr_widths_sbt[iOffset+1];
break;
}
else
{
iColumn = static_cast<unsigned char>(iColumn - y);
iOffset += 2;
}
}
else
{
// COPY phrase.
//
y = 256-y;
if (iColumn < y)
{
iState = tr_widths_sbt[iOffset+iColumn+1];
break;
}
else
{
iColumn = static_cast<unsigned char>(iColumn - y);
iOffset = static_cast<unsigned short>(iOffset + y + 1);
}
}
}
} while (iState < TR_WIDTHS_ACCEPTING_STATES_START);
return (iState - TR_WIDTHS_ACCEPTING_STATES_START);
}
// C-callable wrapper for use from color_ops.c (libmux.so internal).
//
extern "C" LIBMUX_API int co_console_width(const unsigned char *pCodePoint)
{
return ConsoleWidth(pCodePoint);
}
// ColorTable — maps ASCII characters to color index flags.
//
const unsigned int ColorTable[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00-0x0F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x1F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20-0x2F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30-0x3F
// 0x40-0x47
0,
0,
COLOR_INDEX_BG + COLOR_INDEX_BLUE,
COLOR_INDEX_BG + COLOR_INDEX_CYAN,
0,
0,
0,
COLOR_INDEX_BG + COLOR_INDEX_GREEN,
// 0x48-0x4F
0,
0,
0,
0,
0,
COLOR_INDEX_BG + COLOR_INDEX_MAGENTA,
0,
0,
// 0x50-0x57
//
0,
0,
COLOR_INDEX_BG + COLOR_INDEX_RED,
0,
0,
0,
0,
COLOR_INDEX_BG + COLOR_INDEX_WHITE,
// 0x58-0x5F
//
COLOR_INDEX_BG + COLOR_INDEX_BLACK,
COLOR_INDEX_BG + COLOR_INDEX_YELLOW,
0,
0,
0,
0,
0,
0,
// 0x60-0x67
0,
0,
COLOR_INDEX_FG + COLOR_INDEX_BLUE,
COLOR_INDEX_FG + COLOR_INDEX_CYAN,
0,
0,
COLOR_INDEX_BLINK,
COLOR_INDEX_FG + COLOR_INDEX_GREEN,
// 0x68-0x6F
//
COLOR_INDEX_INTENSE,
COLOR_INDEX_INVERSE,
0,
0,
0,
COLOR_INDEX_FG + COLOR_INDEX_MAGENTA,
COLOR_INDEX_RESET,
0,
// 0x70-0x77
//
0,
0,
COLOR_INDEX_FG + COLOR_INDEX_RED,
0,
0,
COLOR_INDEX_UNDERLINE,
0,
COLOR_INDEX_FG + COLOR_INDEX_WHITE,
// 0x78-0x7F
//
COLOR_INDEX_FG + COLOR_INDEX_BLACK,
COLOR_INDEX_FG + COLOR_INDEX_YELLOW,
0,
0,
0,
0,
0,
0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80-0x8F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90-0x9F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0-0xAF
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0-0xBF
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0-0xCF
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0-0xDF
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0-0xEF
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0-0xFF
};
bool parse_rgb(size_t n, const UTF8 *p, RGB &rgb)
{
UTF8 ch;
if ( 7 == n
&& '#' == p[0])
{
// Look for RRGGBB in hexidecimal.
//
for (int i = 1; i < 7; i++)
{
ch = p[i];
if ( !('0' <= ch && ch <= '9')
&& !('a' <= ch && ch <= 'f')
&& !('A' <= ch && ch <= 'F'))
{
return false;
}
}
rgb.r = (mux_hex2dec(p[1]) << 4) | mux_hex2dec(p[2]);
rgb.g = (mux_hex2dec(p[3]) << 4) | mux_hex2dec(p[4]);
rgb.b = (mux_hex2dec(p[5]) << 4) | mux_hex2dec(p[6]);
return true;
}
int nSpaces = 0;
int nDigits = 0;
for (size_t i = 0; i < n; i++)
{
ch = p[i];
if (mux_isspace(ch))
{
if ( 3 < nDigits
|| 0 == nDigits
|| 1 < nSpaces)
{
return false;
}
if (0 == nSpaces)
{
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373) Completes the sweep the issue called for. mux_atol returns long, which is 32-bit on LLP64, so every caller silently truncated on Windows. Two of those were real defects (the truthiness family and cf_size, fixed in the preceding commits); the rest were latent, waiting for a value large enough to matter. Rather than audit 290 sites for whether each can reach 2^31 today, use the 64-bit parser everywhere and remove the class. A dbref cannot overflow now, but nothing stops a later caller passing that same site a timestamp or a byte count. Pure 1:1 substitution: 285 lines changed, and every removed line contained mux_atol while every added line contains mux_atoi64. No control flow, no types, no behaviour beyond the wider parse. This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so the generated code there is unchanged. It only widens the parse on Windows. Narrowing destinations are unaffected either way: `int x = mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both models. Left alone: mux_atol itself in mathutil, its declaration, and three comments that name it. Callers that genuinely want 32-bit semantics can still ask for them; none appear to. Verified on Windows: full solution builds clean with no new warnings, smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched -- identical to before the sweep, with the same 16 build-configuration failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST). Spot checks after the change: the boolean family returns 1 for multiples of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited, and arithmetic, string and list functions are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
rgb.r = mux_atoi64(p+i-nDigits);
if (rgb.r < 0 || 255 < rgb.r)
{
return false;
}
}
else
{
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373) Completes the sweep the issue called for. mux_atol returns long, which is 32-bit on LLP64, so every caller silently truncated on Windows. Two of those were real defects (the truthiness family and cf_size, fixed in the preceding commits); the rest were latent, waiting for a value large enough to matter. Rather than audit 290 sites for whether each can reach 2^31 today, use the 64-bit parser everywhere and remove the class. A dbref cannot overflow now, but nothing stops a later caller passing that same site a timestamp or a byte count. Pure 1:1 substitution: 285 lines changed, and every removed line contained mux_atol while every added line contains mux_atoi64. No control flow, no types, no behaviour beyond the wider parse. This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so the generated code there is unchanged. It only widens the parse on Windows. Narrowing destinations are unaffected either way: `int x = mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both models. Left alone: mux_atol itself in mathutil, its declaration, and three comments that name it. Callers that genuinely want 32-bit semantics can still ask for them; none appear to. Verified on Windows: full solution builds clean with no new warnings, smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched -- identical to before the sweep, with the same 16 build-configuration failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST). Spot checks after the change: the boolean family returns 1 for multiples of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited, and arithmetic, string and list functions are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
rgb.g = mux_atoi64(p+i-nDigits);
if (rgb.g < 0 || 255 < rgb.g)
{
return false;
}
}
nDigits = 0;
nSpaces++;
}
else if (!mux_isdigit(ch))
{
return false;
}
else
{
nDigits++;
}
}
if ( 3 < nDigits
|| 0 == nDigits
|| 2 != nSpaces)
{
return false;
}
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373) Completes the sweep the issue called for. mux_atol returns long, which is 32-bit on LLP64, so every caller silently truncated on Windows. Two of those were real defects (the truthiness family and cf_size, fixed in the preceding commits); the rest were latent, waiting for a value large enough to matter. Rather than audit 290 sites for whether each can reach 2^31 today, use the 64-bit parser everywhere and remove the class. A dbref cannot overflow now, but nothing stops a later caller passing that same site a timestamp or a byte count. Pure 1:1 substitution: 285 lines changed, and every removed line contained mux_atol while every added line contains mux_atoi64. No control flow, no types, no behaviour beyond the wider parse. This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so the generated code there is unchanged. It only widens the parse on Windows. Narrowing destinations are unaffected either way: `int x = mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both models. Left alone: mux_atol itself in mathutil, its declaration, and three comments that name it. Callers that genuinely want 32-bit semantics can still ask for them; none appear to. Verified on Windows: full solution builds clean with no new warnings, smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched -- identical to before the sweep, with the same 16 build-configuration failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST). Spot checks after the change: the boolean family returns 1 for multiples of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited, and arithmetic, string and list functions are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
rgb.b = mux_atoi64(p+n-nDigits-1);
if (rgb.b < 0 || 255 < rgb.b)
{
return false;
}
return true;
}
// sRGB to CIELAB conversion (D65 illuminant)
//
// Pipeline: sRGB (0-255) -> linear RGB (0-1) -> XYZ (D65) -> CIELAB
// Output is integer-scaled: L*100, a*100, b*100
//
static inline double srgb_to_linear(int c)
{
double v = c / 255.0;
return (v <= 0.04045) ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4);
}
static inline double cie_f(double t)
{
return (t > 0.008856) ? cbrt(t) : 7.787 * t + 16.0 / 116.0;
}
inline void rgb2lab(RGB *rgb, LABi *labi)
{
double lr = srgb_to_linear(rgb->r);
double lg = srgb_to_linear(rgb->g);
double lb = srgb_to_linear(rgb->b);
double x = (lr * 0.4124564 + lg * 0.3575761 + lb * 0.1804375) / 0.95047;
double y = (lr * 0.2126729 + lg * 0.7151522 + lb * 0.0721750) / 1.00000;
double z = (lr * 0.0193339 + lg * 0.1191920 + lb * 0.9503041) / 1.08883;
double fx = cie_f(x), fy = cie_f(y), fz = cie_f(z);
double L = 116.0 * fy - 16.0;
double a = 500.0 * (fx - fy);
double b = 200.0 * (fy - fz);
labi->L = static_cast<int>(L * 100.0 + 0.5);
labi->a = static_cast<int>(a * 100.0 + (a >= 0 ? 0.5 : -0.5));
labi->b = static_cast<int>(b * 100.0 + (b >= 0 ? 0.5 : -0.5));
}
2010-08-11 15:50:24 -07:00
inline void cs2rgb(ColorState cs, RGB *rgb)
{
2010-08-11 18:15:00 -07:00
rgb->r = static_cast<int>((cs & 0xFF0000) >> 16);
rgb->g = static_cast<int>((cs & 0x00FF00) >> 8);
rgb->b = static_cast<int>((cs & 0x0000FF));
}
2010-08-11 15:50:24 -07:00
inline ColorState rgb2cs(RGB *rgb)
{
2010-08-11 15:50:24 -07:00
ColorState cs;
cs = (static_cast<ColorState>(rgb->r) << 16)
| (static_cast<ColorState>(rgb->g) << 8)
| (static_cast<ColorState>(rgb->b));
return cs;
}
// All 256 entries of the palette are included in this table, but the palette
2011-03-31 18:06:01 -07:00
// is divided into two non-overlapping trees -- one for when xterm is supported,
// and one for when it is not.
//
// Since elements 0 through 15 do not have dependable RGB vales, we usually
// avoid using them when we have xterm support. xterm uses the values below,
// but other clients do not, and these values are usually user-configurable.
// However, if we are forced to map to the 16-color palette, the 16-color tree
// is used.
//
#define PALETTE16_ROOT 2
#define PALETTE256_ROOT 72
#define PALETTE_SIZE (sizeof(palette)/sizeof(palette[0]))
PALETTE_ENTRY palette[] =
{
{ { 0, 0, 0 }, { 0, 0, 0 }, { 12, 8 }, 0, 0},
{ { 187, 0, 0 }, { 3884, 6344, 5323 }, { 0, 5 }, 1, 1},
{ { 0, 187, 0 }, { 6617, -6826, 6588 }, { 1, 11 }, 2, 2},
{ { 187, 187, 0 }, { 7361, -1707, 7483 }, { -1, -1 }, 3, 3},
{ { 0, 0, 187 }, { 2225, 6272, -8543 }, { -1, -1 }, 4, 4},
{ { 187, 0, 187 }, { 4445, 7781, -4818 }, { 13, 9 }, 5, 5},
{ { 0, 187, 187 }, { 6884, -3809, -1119 }, { 14, 10 }, 6, 6},
{ { 187, 187, 187 }, { 7588, 0, 0 }, { -1, -1 }, 7, 7},
{ { 85, 85, 85 }, { 3615, 0, 0 }, { -1, -1 }, 0, 8},
{ { 255, 85, 85 }, { 6027, 6421, 3655 }, { -1, -1 }, 1, 9},
{ { 85, 255, 85 }, { 8897, -7423, 6593 }, { -1, -1 }, 2, 10},
{ { 255, 255, 85 }, { 9741, -1942, 7731 }, { 6, 15 }, 3, 11},
{ { 85, 85, 255 }, { 4651, 5110, -8420 }, { 4, -1 }, 4, 12},
{ { 255, 85, 255 }, { 6573, 8258, -5221 }, { -1, -1 }, 5, 13},
{ { 85, 255, 255 }, { 9198, -4245, -1274 }, { -1, -1 }, 6, 14},
{ { 255, 255, 255 }, { 10000, 0, 0 }, { 7, 3 }, 7, 15},
{ { 0, 0, 0 }, { 0, 0, 0 }, { -1, -1 }, 0, 0},
{ { 0, 0, 95 }, { 746, 3839, -5234 }, { -1, -1 }, 4, 4},
{ { 0, 0, 135 }, { 1411, 4937, -6724 }, { 20, 54 }, 4, 4},
{ { 0, 0, 175 }, { 2042, 5971, -8133 }, { -1, -1 }, 4, 4},
{ { 0, 0, 215 }, { 2646, 6962, -9483 }, { 19, 55 }, 4, 4},
{ { 0, 0, 255 }, { 3230, 7919, -10786 }, { -1, -1 }, 4, 4},
{ { 0, 95, 0 }, { 3436, -4184, 4038 }, { -1, -1 }, 2, 2},
{ { 0, 95, 95 }, { 3600, -2335, -686 }, { 16, 24 }, 6, 8},
{ { 0, 95, 135 }, { 3772, -828, -2884 }, { -1, -1 }, 6, 8},
{ { 0, 95, 175 }, { 4004, 805, -4908 }, { -1, -1 }, 7, 8},
{ { 0, 95, 215 }, { 4290, 2423, -6767 }, { -1, -1 }, 4, 12},
{ { 0, 95, 255 }, { 4618, 3961, -8484 }, { 56, 170 }, 4, 12},
{ { 0, 135, 0 }, { 4867, -5373, 5185 }, { -1, -1 }, 2, 2},
{ { 0, 135, 95 }, { 4968, -4147, 1287 }, { 22, 65 }, 6, 6},
{ { 0, 135, 135 }, { 5078, -2998, -881 }, { -1, -1 }, 6, 6},
{ { 0, 135, 175 }, { 5231, -1609, -2967 }, { -1, -1 }, 6, 6},
{ { 0, 135, 215 }, { 5427, -98, -4935 }, { 31, 67 }, 7, 8},
{ { 0, 135, 255 }, { 5663, 1444, -6783 }, { -1, -1 }, 4, 12},
{ { 0, 175, 0 }, { 6222, -6498, 6272 }, { -1, -1 }, 2, 2},
{ { 0, 175, 95 }, { 6291, -5627, 3055 }, { 245, 70 }, 2, 2},
{ { 0, 175, 135 }, { 6368, -4753, 999 }, { -1, -1 }, 6, 6},
{ { 0, 175, 175 }, { 6477, -3626, -1066 }, { -1, -1 }, 6, 6},
{ { 0, 175, 215 }, { 6618, -2318, -3066 }, { -1, -1 }, 6, 6},
{ { 0, 175, 255 }, { 6793, -902, -4979 }, { -1, -1 }, 6, 6},
{ { 0, 215, 0 }, { 7520, -7577, 7313 }, { 77, 112 }, 2, 2},
{ { 0, 215, 95 }, { 7571, -6924, 4642 }, { -1, -1 }, 2, 2},
{ { 0, 215, 135 }, { 7628, -6244, 2736 }, { -1, -1 }, 2, 2},
{ { 0, 215, 175 }, { 7710, -5332, 741 }, { -1, -1 }, 6, 6},
{ { 0, 215, 215 }, { 7817, -4228, -1242 }, { 43, 80 }, 6, 6},
{ { 0, 215, 255 }, { 7951, -2980, -3174 }, { 38, 81 }, 6, 6},
{ { 0, 255, 0 }, { 8773, -8618, 8318 }, { -1, -1 }, 2, 10},
{ { 0, 255, 95 }, { 8813, -8108, 6078 }, { -1, -1 }, 2, 10},
{ { 0, 255, 135 }, { 8857, -7565, 4337 }, { 113, 155 }, 2, 10},
{ { 0, 255, 175 }, { 8921, -6819, 2441 }, { -1, -1 }, 2, 10},
{ { 0, 255, 215 }, { 9005, -5890, 505 }, { -1, -1 }, 6, 14},
{ { 0, 255, 255 }, { 9111, -4809, -1413 }, { 50, 86 }, 6, 14},
{ { 95, 0, 0 }, { 1762, 3888, 2721 }, { -1, -1 }, 1, 1},
{ { 95, 0, 95 }, { 2106, 4769, -2953 }, { -1, -1 }, 5, 5},
{ { 95, 0, 135 }, { 2427, 5511, -5011 }, { 17, 91 }, 5, 5},
{ { 95, 0, 175 }, { 2819, 6350, -6819 }, { -1, -1 }, 4, 4},
{ { 95, 0, 215 }, { 3257, 7228, -8450 }, { 18, 92 }, 4, 4},
{ { 95, 0, 255 }, { 3721, 8116, -9954 }, { 21, 93 }, 4, 4},
{ { 95, 95, 0 }, { 3893, -1046, 4587 }, { 29, 64 }, 3, 3},
{ { 95, 95, 95 }, { 4032, 0, 0 }, { 61, 101 }, 7, 8},
{ { 95, 95, 135 }, { 4179, 972, -2218 }, { 25, 26 }, 7, 8},
{ { 95, 95, 175 }, { 4382, 2136, -4283 }, { 236, 68 }, 5, 8},
{ { 95, 95, 215 }, { 4634, 3391, -6192 }, { 99, 134 }, 4, 12},
{ { 95, 95, 255 }, { 4930, 4665, -7961 }, { -1, -1 }, 4, 12},
{ { 95, 135, 0 }, { 5157, -3111, 5536 }, { 28, 100 }, 3, 3},
{ { 95, 135, 95 }, { 5249, -2237, 1719 }, { -1, -1 }, 6, 8},
{ { 95, 135, 135 }, { 5350, -1376, -446 }, { 30, 37 }, 7, 8},
{ { 95, 135, 175 }, { 5492, -286, -2541 }, { -1, -1 }, 7, 8},
{ { 95, 135, 215 }, { 5675, 952, -4526 }, { 103, 104 }, 7, 7},
{ { 95, 135, 255 }, { 5895, 2267, -6396 }, { 33, 105 }, 4, 12},
{ { 95, 175, 0 }, { 6424, -4820, 6517 }, { 34, 71 }, 2, 2},
{ { 95, 175, 95 }, { 6490, -4117, 3349 }, { -1, -1 }, 2, 2},
{ { 95, 175, 135 }, { 6562, -3396, 1301 }, { 140, 228 }, 6, 6},
{ { 95, 175, 175 }, { 6666, -2446, -763 }, { -1, -1 }, 6, 6},
{ { 95, 175, 215 }, { 6802, -1319, -2768 }, { 39, 75 }, 6, 6},
{ { 95, 175, 255 }, { 6969, -71, -4690 }, { -1, -1 }, 7, 7},
{ { 95, 215, 0 }, { 7670, -6288, 7495 }, { -1, -1 }, 2, 2},
{ { 95, 215, 95 }, { 7720, -5722, 4854 }, { 41, 47 }, 2, 2},
{ { 95, 215, 135 }, { 7774, -5127, 2957 }, { 42, 114 }, 2, 2},
{ { 95, 215, 175 }, { 7853, -4321, 966 }, { 44, 78 }, 6, 6},
{ { 95, 215, 215 }, { 7958, -3332, -1018 }, { -1, -1 }, 6, 6},
{ { 95, 215, 255 }, { 8088, -2201, -2952 }, { -1, -1 }, 6, 6},
{ { 95, 255, 0 }, { 8890, -7597, 8460 }, { -1, -1 }, 2, 10},
{ { 95, 255, 95 }, { 8929, -7135, 6239 }, { -1, -1 }, 2, 10},
{ { 95, 255, 135 }, { 8972, -6642, 4506 }, { 83, 120 }, 2, 10},
{ { 95, 255, 175 }, { 9034, -5961, 2614 }, { 49, 121 }, 2, 10},
{ { 95, 255, 215 }, { 9117, -5106, 680 }, { -1, -1 }, 6, 14},
{ { 95, 255, 255 }, { 9221, -4104, -1238 }, { 122, 194 }, 6, 14},
{ { 135, 0, 0 }, { 2717, 4993, 4014 }, { 52, 131 }, 1, 1},
{ { 135, 0, 95 }, { 2936, 5573, -1590 }, { 53, 97 }, 5, 5},
{ { 135, 0, 135 }, { 3158, 6124, -3792 }, { 27, 133 }, 5, 5},
{ { 135, 0, 175 }, { 3449, 6804, -5761 }, { -1, -1 }, 5, 5},
{ { 135, 0, 215 }, { 3795, 7565, -7543 }, { 57, 128 }, 4, 4},
{ { 135, 0, 255 }, { 4180, 8371, -9179 }, { -1, -1 }, 4, 4},
{ { 135, 95, 0 }, { 4327, 913, 5093 }, { -1, -1 }, 3, 3},
{ { 135, 95, 95 }, { 4447, 1631, 651 }, { 243, 130 }, 7, 8},
{ { 135, 95, 135 }, { 4575, 2337, -1577 }, { -1, -1 }, 7, 8},
{ { 135, 95, 175 }, { 4753, 3230, -3670 }, { -1, -1 }, 5, 5},
{ { 135, 95, 215 }, { 4979, 4244, -5618 }, { -1, -1 }, 5, 12},
{ { 135, 95, 255 }, { 5246, 5322, -7432 }, { 63, 135 }, 4, 12},
{ { 135, 135, 0 }, { 5453, -1344, 5890 }, { -1, -1 }, 3, 3},
{ { 135, 135, 95 }, { 5539, -677, 2158 }, { 241, 246 }, 7, 8},
{ { 135, 135, 135 }, { 5632, 0, 0 }, { -1, -1 }, 7, 7},
{ { 135, 135, 175 }, { 5763, 883, -2102 }, { 32, 66 }, 7, 7},
{ { 135, 135, 215 }, { 5933, 1918, -4102 }, { 69, 139 }, 7, 7},
{ { 135, 135, 255 }, { 6139, 3051, -5992 }, { -1, -1 }, 5, 12},
{ { 135, 175, 0 }, { 6637, -3334, 6775 }, { -1, -1 }, 3, 3},
{ { 135, 175, 95 }, { 6700, -2753, 3658 }, { 106, 149 }, 3, 3},
{ { 135, 175, 135 }, { 6769, -2148, 1621 }, { 73, -1 }, 7, 7},
{ { 135, 175, 175 }, { 6868, -1338, -441 }, { -1, -1 }, 7, 7},
{ { 135, 175, 215 }, { 6998, -359, -2451 }, { 74, 248 }, 7, 7},
{ { 135, 175, 255 }, { 7157, 745, -4381 }, { 110, 212 }, 7, 7},
{ { 135, 215, 0 }, { 7832, -5059, 7691 }, { 76, 46 }, 2, 2},
{ { 135, 215, 95 }, { 7880, -4565, 5082 }, { 40, 142 }, 2, 2},
{ { 135, 215, 135 }, { 7933, -4042, 3195 }, { -1, -1 }, 6, 6},
{ { 135, 215, 175 }, { 8009, -3327, 1209 }, { 79, 116 }, 6, 6},
{ { 135, 215, 215 }, { 8110, -2441, -775 }, { 45, 108 }, 6, 6},
{ { 135, 215, 255 }, { 8236, -1415, -2712 }, { 146, 153 }, 7, 7},
{ { 135, 255, 0 }, { 9017, -6577, 8614 }, { 82, 154 }, 2, 10},
{ { 135, 255, 95 }, { 9055, -6160, 6414 }, { 84, 118 }, 2, 10},
{ { 135, 255, 135 }, { 9097, -5712, 4689 }, { -1, -1 }, 2, 10},
{ { 135, 255, 175 }, { 9158, -5090, 2803 }, { -1, -1 }, 6, 14},
{ { 135, 255, 215 }, { 9239, -4305, 871 }, { 51, 85 }, 6, 14},
{ { 135, 255, 255 }, { 9340, -3378, -1048 }, { -1, -1 }, 6, 14},
{ { 175, 0, 0 }, { 3621, 6039, 5057 }, { 132, 125 }, 1, 1},
{ { 175, 0, 95 }, { 3774, 6450, -244 }, { 162, 161 }, 5, 9},
{ { 175, 0, 135 }, { 3935, 6865, -2513 }, { -1, -1 }, 5, 5},
{ { 175, 0, 175 }, { 4155, 7407, -4586 }, { -1, -1 }, 5, 5},
{ { 175, 0, 215 }, { 4426, 8046, -6485 }, { 127, -1 }, 5, 5},
{ { 175, 0, 255 }, { 4741, 8752, -8236 }, { -1, -1 }, 4, 4},
{ { 175, 95, 0 }, { 4864, 2733, 5703 }, { 94, -1 }, 1, 1},
{ { 175, 95, 95 }, { 4965, 3235, 1454 }, { -1, -1 }, 7, 8},
{ { 175, 95, 135 }, { 5075, 3748, -774 }, { 89, 88 }, 7, 8},
{ { 175, 95, 175 }, { 5228, 4425, -2893 }, { 124, 204 }, 5, 5},
{ { 175, 95, 215 }, { 5424, 5228, -4881 }, { 98, 141 }, 5, 5},
{ { 175, 95, 255 }, { 5660, 6118, -6741 }, { -1, -1 }, 5, 12},
{ { 175, 135, 0 }, { 5846, 507, 6350 }, { -1, -1 }, 3, 3},
{ { 175, 135, 95 }, { 5922, 1007, 2735 }, { -1, -1 }, 7, 7},
{ { 175, 135, 135 }, { 6006, 1527, 589 }, { 137, 247 }, 7, 7},
{ { 175, 135, 175 }, { 6125, 2223, -1518 }, { 96, -1 }, 7, 7},
{ { 175, 135, 215 }, { 6280, 3063, -3534 }, { 59, 90 }, 7, 7},
{ { 175, 135, 255 }, { 6469, 4011, -5447 }, { -1, -1 }, 5, 12},
{ { 175, 175, 0 }, { 6931, -1625, 7124 }, { 107, 184 }, 3, 3},
{ { 175, 175, 95 }, { 6990, -1160, 4080 }, { -1, -1 }, 3, 3},
{ { 175, 175, 135 }, { 7054, -669, 2058 }, { -1, -1 }, 7, 7},
{ { 175, 175, 175 }, { 7147, 0, 0 }, { -1, -1 }, 7, 7},
{ { 175, 175, 215 }, { 7268, 824, -2014 }, { -1, -1 }, 7, 7},
{ { 175, 175, 255 }, { 7418, 1772, -3954 }, { 189, 182 }, 7, 7},
{ { 175, 215, 0 }, { 8058, -3551, 7963 }, { -1, -1 }, 3, 3},
{ { 175, 215, 95 }, { 8104, -3135, 5399 }, { -1, -1 }, 3, 3},
{ { 175, 215, 135 }, { 8155, -2689, 3528 }, { 151, 48 }, 3, 3},
{ { 175, 215, 175 }, { 8228, -2074, 1548 }, { 115, 87 }, 7, 7},
{ { 175, 215, 215 }, { 8324, -1303, -434 }, { 250, 195 }, 7, 7},
{ { 175, 215, 255 }, { 8445, -399, -2375 }, { -1, -1 }, 7, 7},
{ { 175, 255, 0 }, { 9197, -5270, 8831 }, { -1, -1 }, 2, 10},
{ { 175, 255, 95 }, { 9234, -4904, 6661 }, { 119, 227 }, 2, 10},
{ { 175, 255, 135 }, { 9274, -4508, 4948 }, { -1, -1 }, 2, 10},
{ { 175, 255, 175 }, { 9333, -3956, 3070 }, { -1, -1 }, 6, 14},
{ { 175, 255, 215 }, { 9411, -3254, 1142 }, { 123, 159 }, 6, 14},
{ { 175, 255, 255 }, { 9510, -2417, -777 }, { -1, -1 }, 6, 14},
{ { 215, 0, 0 }, { 4487, 7041, 5908 }, { -1, -1 }, 1, 1},
{ { 215, 0, 95 }, { 4601, 7349, 1053 }, { 160, -1 }, 1, 9},
{ { 215, 0, 135 }, { 4724, 7671, -1235 }, { 126, 163 }, 5, 5},
{ { 215, 0, 175 }, { 4894, 8105, -3368 }, { -1, -1 }, 5, 5},
{ { 215, 0, 215 }, { 5110, 8636, -5348 }, { -1, -1 }, 5, 5},
{ { 215, 0, 255 }, { 5367, 9245, -7188 }, { 129, 201 }, 5, 13},
{ { 215, 95, 0 }, { 5470, 4355, 6373 }, { -1, -1 }, 1, 1},
{ { 215, 95, 95 }, { 5554, 4720, 2349 }, { 169, 202 }, 1, 9},
{ { 215, 95, 135 }, { 5647, 5103, 135 }, { -1, -1 }, 7, 9},
{ { 215, 95, 175 }, { 5778, 5623, -2000 }, { 168, 174 }, 5, 5},
{ { 215, 95, 215 }, { 5947, 6260, -4020 }, { 62, 171 }, 5, 5},
{ { 215, 95, 255 }, { 6153, 6990, -5924 }, { 165, 200 }, 5, 13},
{ { 215, 135, 0 }, { 6316, 2286, 6890 }, { 136, -1 }, 3, 3},
{ { 215, 135, 95 }, { 6384, 2663, 3419 }, { 138, 172 }, 7, 9},
{ { 215, 135, 135 }, { 6459, 3063, 1294 }, { -1, -1 }, 7, 7},
{ { 215, 135, 175 }, { 6565, 3610, -813 }, { -1, -1 }, 7, 7},
{ { 215, 135, 215 }, { 6704, 4286, -2843 }, { 207, 177 }, 5, 5},
{ { 215, 135, 255 }, { 6875, 5069, -4779 }, { -1, -1 }, 5, 13},
{ { 215, 175, 0 }, { 7296, 143, 7553 }, { 143, 179 }, 3, 3},
{ { 215, 175, 95 }, { 7350, 512, 4600 }, { -1, -1 }, 3, 3},
{ { 215, 175, 135 }, { 7410, 906, 2601 }, { 181, 178 }, 7, 7},
{ { 215, 175, 175 }, { 7495, 1450, 549 }, { 144, 252 }, 7, 7},
{ { 215, 175, 215 }, { 7608, 2132, -1468 }, { 183, 225 }, 7, 7},
{ { 215, 175, 255 }, { 7748, 2932, -3418 }, { 213, 219 }, 7, 7},
{ { 215, 215, 0 }, { 8347, -1895, 8306 }, { 148, -1 }, 3, 3},
{ { 215, 215, 95 }, { 8390, -1549, 5801 }, { 214, 254 }, 3, 3},
{ { 215, 215, 135 }, { 8438, -1177, 3949 }, { -1, -1 }, 3, 3},
{ { 215, 215, 175 }, { 8507, -658, 1980 }, { 253, 220 }, 7, 7},
{ { 215, 215, 215 }, { 8598, 0, 0 }, { -1, -1 }, 7, 7},
{ { 215, 215, 255 }, { 8713, 781, -1944 }, { 117, 152 }, 7, 7},
{ { 215, 255, 0 }, { 9430, -3767, 9110 }, { -1, -1 }, 3, 11},
{ { 215, 255, 95 }, { 9465, -3451, 6978 }, { 156, 192 }, 3, 11},
{ { 215, 255, 135 }, { 9504, -3109, 5283 }, { -1, -1 }, 3, 11},
{ { 215, 255, 175 }, { 9561, -2628, 3414 }, { 157, -1 }, 3, 15},
{ { 215, 255, 215 }, { 9636, -2012, 1491 }, { 158, 193 }, 7, 15},
{ { 215, 255, 255 }, { 9730, -1272, -427 }, { -1, -1 }, 7, 15},
{ { 255, 0, 0 }, { 5324, 8009, 6720 }, { -1, -1 }, 1, 1},
{ { 255, 0, 95 }, { 5413, 8249, 2291 }, { 196, -1 }, 1, 9},
{ { 255, 0, 135 }, { 5509, 8505, 17 }, { 205, 197 }, 5, 9},
{ { 255, 0, 175 }, { 5645, 8859, -2145 }, { -1, -1 }, 5, 5},
{ { 255, 0, 215 }, { 5820, 9303, -4177 }, { 164, -1 }, 5, 13},
{ { 255, 0, 255 }, { 6032, 9823, -6082 }, { -1, -1 }, 5, 13},
{ { 255, 95, 0 }, { 6118, 5801, 7073 }, { 166, 203 }, 1, 1},
{ { 255, 95, 95 }, { 6189, 6077, 3294 }, { -1, -1 }, 1, 9},
{ { 255, 95, 135 }, { 6268, 6372, 1106 }, { 167, 198 }, 1, 9},
{ { 255, 95, 175 }, { 6379, 6781, -1033 }, { 199, 206 }, 5, 5},
{ { 255, 95, 215 }, { 6524, 7293, -3077 }, { -1, -1 }, 5, 13},
{ { 255, 95, 255 }, { 6703, 7895, -5017 }, { -1, -1 }, 5, 13},
{ { 255, 135, 0 }, { 6846, 3935, 7486 }, { -1, -1 }, 1, 1},
{ { 255, 135, 95 }, { 6905, 4226, 4178 }, { 216, 215 }, 1, 9},
{ { 255, 135, 135 }, { 6971, 4538, 2083 }, { -1, -1 }, 1, 9},
{ { 255, 135, 175 }, { 7066, 4971, -18 }, { 175, -1 }, 7, 9},
{ { 255, 135, 215 }, { 7189, 5518, -2058 }, { 176, 211 }, 5, 13},
{ { 255, 135, 255 }, { 7342, 6164, -4013 }, { -1, -1 }, 5, 13},
{ { 255, 175, 0 }, { 7724, 1872, 8047 }, { 180, 209 }, 3, 3},
{ { 255, 175, 95 }, { 7773, 2165, 5200 }, { 208, -1 }, 3, 3},
{ { 255, 175, 135 }, { 7827, 2482, 3230 }, { 210, 217 }, 7, 7},
{ { 255, 175, 175 }, { 7905, 2924, 1190 }, { -1, -1 }, 7, 7},
{ { 255, 175, 215 }, { 8008, 3486, -827 }, { -1, -1 }, 7, 7},
{ { 255, 175, 255 }, { 8137, 4155, -2786 }, { -1, -1 }, 7, 13},
{ { 255, 215, 0 }, { 8693, -192, 8713 }, { 186, 229 }, 3, 11},
{ { 255, 215, 95 }, { 8733, 93, 6278 }, { -1, -1 }, 3, 3},
{ { 255, 215, 135 }, { 8778, 402, 4451 }, { 221, -1 }, 3, 3},
{ { 255, 215, 175 }, { 8843, 836, 2496 }, { 255, 222 }, 7, 15},
{ { 255, 215, 215 }, { 8928, 1392, 520 }, { -1, -1 }, 7, 15},
{ { 255, 215, 255 }, { 9036, 2059, -1425 }, { 218, -1 }, 7, 15},
{ { 255, 255, 0 }, { 9714, -2155, 9448 }, { 190, -1 }, 3, 11},
{ { 255, 255, 95 }, { 9747, -1887, 7362 }, { 191, 226 }, 3, 11},
{ { 255, 255, 135 }, { 9785, -1594, 5688 }, { 150, 251 }, 3, 11},
{ { 255, 255, 175 }, { 9838, -1180, 3833 }, { -1, -1 }, 3, 11},
{ { 255, 255, 215 }, { 9910, -647, 1916 }, { -1, -1 }, 7, 15},
{ { 255, 255, 255 }, { 10000, 0, 0 }, { -1, -1 }, 7, 15},
{ { 8, 8, 8 }, { 219, 0, 0 }, { 23, 234 }, 0, 0},
{ { 18, 18, 18 }, { 546, 0, 0 }, { -1, -1 }, 0, 0},
{ { 28, 28, 28 }, { 1027, 0, 0 }, { 233, 235 }, 0, 0},
{ { 38, 38, 38 }, { 1516, 0, 0 }, { -1, -1 }, 0, 0},
{ { 48, 48, 48 }, { 1987, 0, 0 }, { 232, 237 }, 0, 8},
{ { 58, 58, 58 }, { 2442, 0, 0 }, { 60, 239 }, 0, 8},
{ { 68, 68, 68 }, { 2885, 0, 0 }, { -1, -1 }, 0, 8},
{ { 78, 78, 78 }, { 3318, 0, 0 }, { 238, 240 }, 0, 8},
{ { 88, 88, 88 }, { 3741, 0, 0 }, { -1, -1 }, 0, 8},
{ { 98, 98, 98 }, { 4155, 0, 0 }, { 58, 95 }, 7, 8},
{ { 108, 108, 108 }, { 4563, 0, 0 }, { -1, -1 }, 7, 8},
{ { 118, 118, 118 }, { 4964, 0, 0 }, { 242, 244 }, 7, 8},
{ { 128, 128, 128 }, { 5359, 0, 0 }, { -1, -1 }, 7, 8},
{ { 138, 138, 138 }, { 5748, 0, 0 }, { 102, 36 }, 7, 7},
{ { 148, 148, 148 }, { 6132, 0, 0 }, { 35, 173 }, 7, 7},
{ { 158, 158, 158 }, { 6511, 0, 0 }, { -1, -1 }, 7, 7},
{ { 168, 168, 168 }, { 6887, 0, 0 }, { 109, 145 }, 7, 7},
{ { 178, 178, 178 }, { 7257, 0, 0 }, { 111, 147 }, 7, 7},
{ { 188, 188, 188 }, { 7625, 0, 0 }, { -1, -1 }, 7, 7},
{ { 198, 198, 198 }, { 7988, 0, 0 }, { 249, 185 }, 7, 7},
{ { 208, 208, 208 }, { 8348, 0, 0 }, { -1, -1 }, 7, 7},
{ { 218, 218, 218 }, { 8705, 0, 0 }, { 188, 230 }, 7, 7},
{ { 228, 228, 228 }, { 9059, 0, 0 }, { 187, 223 }, 7, 15},
{ { 238, 238, 238 }, { 9410, 0, 0 }, { 224, 231 }, 7, 15},
};
// CIE76 color difference: squared Euclidean distance in CIELAB.
//
int64_t diff(const LABi &lab1, const LABi &lab2)
{
int64_t dL = static_cast<int64_t>(lab1.L - lab2.L);
int64_t da = static_cast<int64_t>(lab1.a - lab2.a);
int64_t db = static_cast<int64_t>(lab1.b - lab2.b);
int64_t r = dL*dL + da*da + db*db;
return r;
}
void NearestIndex_tree_a(int iHere, const LABi &labi, int &iBest, int64_t &rBest);
void NearestIndex_tree_b(int iHere, const LABi &labi, int &iBest, int64_t &rBest);
void NearestIndex_tree_L(int iHere, const LABi &labi, int &iBest, int64_t &rBest)
{
if (-1 == iHere)
{
return;
}
if (-1 == iBest)
{
iBest = iHere;
rBest = diff(labi, palette[iBest].labi);
}
int64_t rHere = diff(labi, palette[iHere].labi);
if (rHere < rBest)
{
iBest = iHere;
rBest = rHere;
}
int64_t d = static_cast<int64_t>(labi.L - palette[iHere].labi.L);
int iNearChild = (d < 0)?0:1;
NearestIndex_tree_a(palette[iHere].child[iNearChild], labi, iBest, rBest);
int64_t rAxis = d*d;
if (rAxis < rBest)
{
NearestIndex_tree_a(palette[iHere].child[1-iNearChild], labi, iBest, rBest);
}
}
void NearestIndex_tree_a(int iHere, const LABi &labi, int &iBest, int64_t &rBest)
{
if (-1 == iHere)
{
return;
}
if (-1 == iBest)
{
iBest = iHere;
rBest = diff(labi, palette[iBest].labi);
}
int64_t rHere = diff(labi, palette[iHere].labi);
if (rHere < rBest)
{
iBest = iHere;
rBest = rHere;
}
int64_t d = static_cast<int64_t>(labi.a - palette[iHere].labi.a);
int iNearChild = (d < 0)?0:1;
NearestIndex_tree_b(palette[iHere].child[iNearChild], labi, iBest, rBest);
int64_t rAxis = d*d;
if (rAxis < rBest)
{
NearestIndex_tree_b(palette[iHere].child[1-iNearChild], labi, iBest, rBest);
}
}
void NearestIndex_tree_b(int iHere, const LABi &labi, int &iBest, int64_t &rBest)
{
if (-1 == iHere)
{
return;
}
if (-1 == iBest)
{
iBest = iHere;
rBest = diff(labi, palette[iBest].labi);
}
int64_t rHere = diff(labi, palette[iHere].labi);
if (rHere < rBest)
{
iBest = iHere;
rBest = rHere;
}
int64_t d = static_cast<int64_t>(labi.b - palette[iHere].labi.b);
int iNearChild = (d < 0)?0:1;
NearestIndex_tree_L(palette[iHere].child[iNearChild], labi, iBest, rBest);
int64_t rAxis = d*d;
if (rAxis < rBest)
{
NearestIndex_tree_L(palette[iHere].child[1-iNearChild], labi, iBest, rBest);
}
}
int FindNearestPaletteEntry(RGB &rgb, bool fColor256)
{
LABi labi;
rgb2lab(&rgb, &labi);
int64_t d;
int j = -1;
NearestIndex_tree_L(fColor256 ? PALETTE256_ROOT : PALETTE16_ROOT, labi, j, d);
return j;
}
int FindNearestPalette8Entry(RGB &rgb)
{
LABi labi;
rgb2lab(&rgb, &labi);
int iNearest = 0;
int64_t rNearest = diff(labi, palette[0].labi);
for (int i = 1; i < 8; i++)
{
int64_t r = diff(labi, palette[i].labi);
if (r < rNearest)
{
rNearest = r;
iNearest = i;
}
}
return iNearest;
}
// C-callable wrappers for CIE97 + K-d tree nearest-color search.
//
extern "C" LIBMUX_API int co_nearest_xterm256(const unsigned char *rgb)
{
RGB c;
c.r = rgb[0];
c.g = rgb[1];
c.b = rgb[2];
return FindNearestPaletteEntry(c, true);
}
extern "C" LIBMUX_API int co_nearest_xterm16(const unsigned char *rgb)
{
RGB c;
c.r = rgb[0];
c.g = rgb[1];
c.b = rgb[2];
return FindNearestPaletteEntry(c, false);
}
#define CS_FOREGROUND UINT64_C(0x0000000001FFFFFF)
2010-08-11 15:50:24 -07:00
#define CS_FOREGROUND_RED UINT64_C(0x0000000000FF0000)
#define CS_FOREGROUND_GREEN UINT64_C(0x000000000000FF00)
#define CS_FOREGROUND_BLUE UINT64_C(0x00000000000000FF)
#define CS_FG_BLACK UINT64_C(0x0000000001000000) // FOREGROUND BLACK (0,0,0)
#define CS_FG_RED UINT64_C(0x0000000001000001) // FOREGROUND RED (187,0,0)
#define CS_FG_GREEN UINT64_C(0x0000000001000002) // FOREGROUND GREEN (0,187,0)
#define CS_FG_YELLOW UINT64_C(0x0000000001000003) // FOREGROUND YELLOW (187,187,0)
#define CS_FG_BLUE UINT64_C(0x0000000001000004) // FOREGROUND BLUE (0,0,187)
#define CS_FG_MAGENTA UINT64_C(0x0000000001000005) // FOREGROUND MAGENTA (187,0,187)
#define CS_FG_CYAN UINT64_C(0x0000000001000006) // FOREGROUND CYAN (0,187,187)
#define CS_FG_WHITE UINT64_C(0x0000000001000007) // FOREGROUND WHITE (187,187,187)
#define CS_FG_INDEXED UINT64_C(0x0000000001000000)
#define CS_FG(x) (CS_FG_INDEXED | static_cast<uint64_t>(x))
#define CS_FG_FIELD(x) ((x) & UINT64_C(0x0000000000FFFFFF))
2022-03-12 14:09:34 -07:00
#define CS_FG_FIELD_INDEXED(x) ((x) & UINT64_C(0x00000000000000FF))
#define CS_FG_DEFAULT CS_FG(NUM_FG)
#define CS_BACKGROUND UINT64_C(0x01FFFFFF00000000)
2010-08-11 15:50:24 -07:00
#define CS_BACKGROUND_RED UINT64_C(0x00FF000000000000)
#define CS_BACKGROUND_GREEN UINT64_C(0x0000FF0000000000)
#define CS_BACKGROUND_BLUE UINT64_C(0x000000FF00000000)
#define CS_BG_BLACK UINT64_C(0x0100000000000000) // BACKGROUND BLACK (0,0,0)
#define CS_BG_RED UINT64_C(0x0100000100000000) // BACKGROUND RED (187,0,0)
#define CS_BG_GREEN UINT64_C(0x0100000200000000) // BACKGROUND GREEN (0,187,0)
#define CS_BG_YELLOW UINT64_C(0x0100000300000000) // BACKGROUND YELLOW (187,187,0)
#define CS_BG_BLUE UINT64_C(0x0100000400000000) // BACKGROUND BLUE (0,0,187)
#define CS_BG_MAGENTA UINT64_C(0x0100000500000000) // BACKGROUND MAGENTA (187,0,187)
#define CS_BG_CYAN UINT64_C(0x0100000600000000) // BACKGROUND CYAN (0,187,187)
#define CS_BG_WHITE UINT64_C(0x0100000700000000) // BACKGROUND WHITE (187,187,187)
#define CS_BG_INDEXED UINT64_C(0x0100000000000000)
#define CS_BG(x) (CS_BG_INDEXED | (static_cast<uint64_t>(x) << 32))
#define CS_BG_FIELD(x) (((x) & UINT64_C(0x00FFFFFF00000000)) >> 32)
2022-03-12 14:09:34 -07:00
#define CS_BG_FIELD_INDEXED(x) (((x) & UINT64_C(0x000000FF00000000)) >> 32)
#define CS_BG_DEFAULT CS_BG(NUM_BG)
#define CS_INTENSE UINT64_C(0x0000000010000000)
#define CS_INVERSE UINT64_C(0x0000000020000000)
#define CS_UNDERLINE UINT64_C(0x0000000040000000)
#define CS_BLINK UINT64_C(0x0000000080000000)
#define CS_ATTRS UINT64_C(0x00000000F0000000)
#define CS_ALLBITS UINT64_C(0x01FFFFFFF1FFFFFF)
#define CS_NORMAL (CS_FG_DEFAULT|CS_BG_DEFAULT)
#define CS_NOBLEED (CS_FG_WHITE|CS_BG_DEFAULT)
// XTERM_FG(0) through XTERM_FG(7) is equivalent to ANSI_BLACK...ANSI_WHITE.
// Even for 256-color-capable clients, the latter are used instead of the former.
// Similiarly for XTERM_BG(0) through XTERM_BG(0).
//
2010-08-11 18:15:00 -07:00
const MUX_COLOR_SET aColors[] =
{
{ 0, 0, "", 0, T(""), 0, T(""), 0}, // COLOR_NOTCOLOR
{ CS_NORMAL, CS_ALLBITS, ANSI_NORMAL, sizeof(ANSI_NORMAL)-1, T(COLOR_RESET), 3, T("%xn"), 3}, // COLOR_INDEX_RESET
{ CS_INTENSE, CS_INTENSE, ANSI_HILITE, sizeof(ANSI_HILITE)-1, T(COLOR_INTENSE), 3, T("%xh"), 3}, // COLOR_INDEX_ATTR, COLOR_INDEX_INTENSE
{ CS_UNDERLINE, CS_UNDERLINE, ANSI_UNDER, sizeof(ANSI_UNDER)-1, T(COLOR_UNDERLINE), 3, T("%xu"), 3}, // COLOR_INDEX_UNDERLINE
{ CS_BLINK, CS_BLINK, ANSI_BLINK, sizeof(ANSI_BLINK)-1, T(COLOR_BLINK), 3, T("%xf"), 3}, // COLOR_INDEX_BLINK
{ CS_INVERSE, CS_INVERSE, ANSI_INVERSE, sizeof(ANSI_INVERSE)-1, T(COLOR_INVERSE), 3, T("%xi"), 3}, // COLOR_INDEX_INVERSE
{ CS_FG_BLACK, CS_FOREGROUND, ANSI_BLACK, sizeof(ANSI_BLACK)-1, T(COLOR_FG_BLACK), 3, T("%xx"), 3}, // COLOR_INDEX_FG
{ CS_FG_RED, CS_FOREGROUND, ANSI_RED, sizeof(ANSI_RED)-1, T(COLOR_FG_RED), 3, T("%xr"), 3},
{ CS_FG_GREEN, CS_FOREGROUND, ANSI_GREEN, sizeof(ANSI_GREEN)-1, T(COLOR_FG_GREEN), 3, T("%xg"), 3},
{ CS_FG_YELLOW, CS_FOREGROUND, ANSI_YELLOW, sizeof(ANSI_YELLOW)-1, T(COLOR_FG_YELLOW), 3, T("%xy"), 3},
{ CS_FG_BLUE, CS_FOREGROUND, ANSI_BLUE, sizeof(ANSI_BLUE)-1, T(COLOR_FG_BLUE), 3, T("%xb"), 3},
{ CS_FG_MAGENTA, CS_FOREGROUND, ANSI_MAGENTA, sizeof(ANSI_MAGENTA)-1, T(COLOR_FG_MAGENTA), 3, T("%xm"), 3},
{ CS_FG_CYAN, CS_FOREGROUND, ANSI_CYAN, sizeof(ANSI_CYAN)-1, T(COLOR_FG_CYAN), 3, T("%xc"), 3},
{ CS_FG_WHITE, CS_FOREGROUND, ANSI_WHITE, sizeof(ANSI_WHITE)-1, T(COLOR_FG_WHITE), 3, T("%xw"), 3}, // COLOR_INDEX_FG_WHITE
{ CS_FG( 8), CS_FOREGROUND, XTERM_FG( 8), sizeof(XTERM_FG( 8))-1, T(COLOR_FG_555555), 3, T("NU8"), 6}, // These eight are converted into something else.
{ CS_FG( 9), CS_FOREGROUND, XTERM_FG( 9), sizeof(XTERM_FG( 9))-1, T(COLOR_FG_FF5555), 3, T("NU9"), 6}, // .
{ CS_FG( 10), CS_FOREGROUND, XTERM_FG( 10), sizeof(XTERM_FG( 10))-1, T(COLOR_FG_55FF55), 3, T("NU10"), 6}, // .
{ CS_FG( 11), CS_FOREGROUND, XTERM_FG( 11), sizeof(XTERM_FG( 11))-1, T(COLOR_FG_FFFF55), 3, T("NU11"), 6}, // .
{ CS_FG( 12), CS_FOREGROUND, XTERM_FG( 12), sizeof(XTERM_FG( 12))-1, T(COLOR_FG_5555FF), 3, T("NU12"), 6}, // .
{ CS_FG( 13), CS_FOREGROUND, XTERM_FG( 13), sizeof(XTERM_FG( 13))-1, T(COLOR_FG_FF55FF), 3, T("NU13"), 6}, // .
{ CS_FG( 14), CS_FOREGROUND, XTERM_FG( 14), sizeof(XTERM_FG( 14))-1, T(COLOR_FG_55FFFF), 3, T("NU14"), 6}, // .
{ CS_FG( 15), CS_FOREGROUND, XTERM_FG( 15), sizeof(XTERM_FG( 15))-1, T(COLOR_FG_FFFFFF_1),3, T("NU15"), 6}, // -
{ CS_FG( 16), CS_FOREGROUND, XTERM_FG( 16), sizeof(XTERM_FG( 16))-1, T(COLOR_FG_000000), 3, T("%x<#000000>"), 11},
{ CS_FG( 17), CS_FOREGROUND, XTERM_FG( 17), sizeof(XTERM_FG( 17))-1, T(COLOR_FG_00005F), 3, T("%x<#00005F>"), 11},
{ CS_FG( 18), CS_FOREGROUND, XTERM_FG( 18), sizeof(XTERM_FG( 18))-1, T(COLOR_FG_000087), 3, T("%x<#000087>"), 11},
{ CS_FG( 19), CS_FOREGROUND, XTERM_FG( 19), sizeof(XTERM_FG( 19))-1, T(COLOR_FG_0000AF), 3, T("%x<#0000AF>"), 11},
{ CS_FG( 20), CS_FOREGROUND, XTERM_FG( 20), sizeof(XTERM_FG( 20))-1, T(COLOR_FG_0000D7), 3, T("%x<#0000D7>"), 11},
{ CS_FG( 21), CS_FOREGROUND, XTERM_FG( 21), sizeof(XTERM_FG( 21))-1, T(COLOR_FG_0000FF), 3, T("%x<#0000FF>"), 11},
{ CS_FG( 22), CS_FOREGROUND, XTERM_FG( 22), sizeof(XTERM_FG( 22))-1, T(COLOR_FG_005F00), 3, T("%x<#005F00>"), 11},
{ CS_FG( 23), CS_FOREGROUND, XTERM_FG( 23), sizeof(XTERM_FG( 23))-1, T(COLOR_FG_005F5F), 3, T("%x<#005F5F>"), 11},
{ CS_FG( 24), CS_FOREGROUND, XTERM_FG( 24), sizeof(XTERM_FG( 24))-1, T(COLOR_FG_005F87), 3, T("%x<#005F87>"), 11},
{ CS_FG( 25), CS_FOREGROUND, XTERM_FG( 25), sizeof(XTERM_FG( 25))-1, T(COLOR_FG_005FAF), 3, T("%x<#005FAF>"), 11},
{ CS_FG( 26), CS_FOREGROUND, XTERM_FG( 26), sizeof(XTERM_FG( 26))-1, T(COLOR_FG_005FD7), 3, T("%x<#005FD7>"), 11},
{ CS_FG( 27), CS_FOREGROUND, XTERM_FG( 27), sizeof(XTERM_FG( 27))-1, T(COLOR_FG_005FFF), 3, T("%x<#005FFF>"), 11},
{ CS_FG( 28), CS_FOREGROUND, XTERM_FG( 28), sizeof(XTERM_FG( 28))-1, T(COLOR_FG_008700), 3, T("%x<#008700>"), 11},
{ CS_FG( 29), CS_FOREGROUND, XTERM_FG( 29), sizeof(XTERM_FG( 29))-1, T(COLOR_FG_00875F), 3, T("%x<#00875F>"), 11},
{ CS_FG( 30), CS_FOREGROUND, XTERM_FG( 30), sizeof(XTERM_FG( 30))-1, T(COLOR_FG_008785), 3, T("%x<#008785>"), 11},
{ CS_FG( 31), CS_FOREGROUND, XTERM_FG( 31), sizeof(XTERM_FG( 31))-1, T(COLOR_FG_0087AF), 3, T("%x<#0087AF>"), 11},
{ CS_FG( 32), CS_FOREGROUND, XTERM_FG( 32), sizeof(XTERM_FG( 32))-1, T(COLOR_FG_0087D7), 3, T("%x<#0087D7>"), 11},
{ CS_FG( 33), CS_FOREGROUND, XTERM_FG( 33), sizeof(XTERM_FG( 33))-1, T(COLOR_FG_0087FF), 3, T("%x<#0087FF>"), 11},
{ CS_FG( 34), CS_FOREGROUND, XTERM_FG( 34), sizeof(XTERM_FG( 34))-1, T(COLOR_FG_00AF00), 3, T("%x<#00AF00>"), 11},
{ CS_FG( 35), CS_FOREGROUND, XTERM_FG( 35), sizeof(XTERM_FG( 35))-1, T(COLOR_FG_00AF5F), 3, T("%x<#00AF5F>"), 11},
{ CS_FG( 36), CS_FOREGROUND, XTERM_FG( 36), sizeof(XTERM_FG( 36))-1, T(COLOR_FG_00AF87), 3, T("%x<#00AF87>"), 11},
{ CS_FG( 37), CS_FOREGROUND, XTERM_FG( 37), sizeof(XTERM_FG( 37))-1, T(COLOR_FG_00AFAF), 3, T("%x<#00AFAF>"), 11},
{ CS_FG( 38), CS_FOREGROUND, XTERM_FG( 38), sizeof(XTERM_FG( 38))-1, T(COLOR_FG_00AFD7), 3, T("%x<#00AFD7>"), 11},
{ CS_FG( 39), CS_FOREGROUND, XTERM_FG( 39), sizeof(XTERM_FG( 39))-1, T(COLOR_FG_00AFFF), 3, T("%x<#00AFFF>"), 11},
{ CS_FG( 40), CS_FOREGROUND, XTERM_FG( 40), sizeof(XTERM_FG( 40))-1, T(COLOR_FG_00D700), 3, T("%x<#00D700>"), 11},
{ CS_FG( 41), CS_FOREGROUND, XTERM_FG( 41), sizeof(XTERM_FG( 41))-1, T(COLOR_FG_00D75F), 3, T("%x<#00D75F>"), 11},
{ CS_FG( 42), CS_FOREGROUND, XTERM_FG( 42), sizeof(XTERM_FG( 42))-1, T(COLOR_FG_00D787), 3, T("%x<#00D787>"), 11},
{ CS_FG( 43), CS_FOREGROUND, XTERM_FG( 43), sizeof(XTERM_FG( 43))-1, T(COLOR_FG_00D7AF), 3, T("%x<#00D7AF>"), 11},
{ CS_FG( 44), CS_FOREGROUND, XTERM_FG( 44), sizeof(XTERM_FG( 44))-1, T(COLOR_FG_00D7D7), 3, T("%x<#00D7D7>"), 11},
{ CS_FG( 45), CS_FOREGROUND, XTERM_FG( 45), sizeof(XTERM_FG( 45))-1, T(COLOR_FG_00D7FF), 3, T("%x<#00D7FF>"), 11},
{ CS_FG( 46), CS_FOREGROUND, XTERM_FG( 46), sizeof(XTERM_FG( 46))-1, T(COLOR_FG_00FF00), 3, T("%x<#00FF00>"), 11},
{ CS_FG( 47), CS_FOREGROUND, XTERM_FG( 47), sizeof(XTERM_FG( 47))-1, T(COLOR_FG_00FF5A), 3, T("%x<#00FF5A>"), 11},
{ CS_FG( 48), CS_FOREGROUND, XTERM_FG( 48), sizeof(XTERM_FG( 48))-1, T(COLOR_FG_00FF87), 3, T("%x<#00FF87>"), 11},
{ CS_FG( 49), CS_FOREGROUND, XTERM_FG( 49), sizeof(XTERM_FG( 49))-1, T(COLOR_FG_00FFAF), 3, T("%x<#00FFAF>"), 11},
{ CS_FG( 50), CS_FOREGROUND, XTERM_FG( 50), sizeof(XTERM_FG( 50))-1, T(COLOR_FG_00FFD7), 3, T("%x<#00FFD7>"), 11},
{ CS_FG( 51), CS_FOREGROUND, XTERM_FG( 51), sizeof(XTERM_FG( 51))-1, T(COLOR_FG_00FFFF), 3, T("%x<#00FFFF>"), 11},
{ CS_FG( 52), CS_FOREGROUND, XTERM_FG( 52), sizeof(XTERM_FG( 52))-1, T(COLOR_FG_5F0000), 3, T("%x<#5F0000>"), 11},
{ CS_FG( 53), CS_FOREGROUND, XTERM_FG( 53), sizeof(XTERM_FG( 53))-1, T(COLOR_FG_5F005F), 3, T("%x<#5F005F>"), 11},
{ CS_FG( 54), CS_FOREGROUND, XTERM_FG( 54), sizeof(XTERM_FG( 54))-1, T(COLOR_FG_5F0087), 3, T("%x<#5F0087>"), 11},
{ CS_FG( 55), CS_FOREGROUND, XTERM_FG( 55), sizeof(XTERM_FG( 55))-1, T(COLOR_FG_5F00AF), 3, T("%x<#5F00AF>"), 11},
{ CS_FG( 56), CS_FOREGROUND, XTERM_FG( 56), sizeof(XTERM_FG( 56))-1, T(COLOR_FG_5F00D7), 3, T("%x<#5F00D7>"), 11},
{ CS_FG( 57), CS_FOREGROUND, XTERM_FG( 57), sizeof(XTERM_FG( 57))-1, T(COLOR_FG_5F00FF), 3, T("%x<#5F00FF>"), 11},
{ CS_FG( 58), CS_FOREGROUND, XTERM_FG( 58), sizeof(XTERM_FG( 58))-1, T(COLOR_FG_5F5F00), 3, T("%x<#5F5F00>"), 11},
{ CS_FG( 59), CS_FOREGROUND, XTERM_FG( 59), sizeof(XTERM_FG( 59))-1, T(COLOR_FG_5F5F5F), 3, T("%x<#5F5F5F>"), 11},
{ CS_FG( 60), CS_FOREGROUND, XTERM_FG( 60), sizeof(XTERM_FG( 60))-1, T(COLOR_FG_5F5F87), 3, T("%x<#5F5F87>"), 11},
{ CS_FG( 61), CS_FOREGROUND, XTERM_FG( 61), sizeof(XTERM_FG( 61))-1, T(COLOR_FG_5F5FAF), 3, T("%x<#5F5FAF>"), 11},
{ CS_FG( 62), CS_FOREGROUND, XTERM_FG( 62), sizeof(XTERM_FG( 62))-1, T(COLOR_FG_5F5FD7), 3, T("%x<#5F5FD7>"), 11},
{ CS_FG( 63), CS_FOREGROUND, XTERM_FG( 63), sizeof(XTERM_FG( 63))-1, T(COLOR_FG_5F5FFF), 3, T("%x<#5F5FFF>"), 11},
{ CS_FG( 64), CS_FOREGROUND, XTERM_FG( 64), sizeof(XTERM_FG( 64))-1, T(COLOR_FG_5F8700), 3, T("%x<#5F8700>"), 11},
{ CS_FG( 65), CS_FOREGROUND, XTERM_FG( 65), sizeof(XTERM_FG( 65))-1, T(COLOR_FG_5F875F), 3, T("%x<#5F875F>"), 11},
{ CS_FG( 66), CS_FOREGROUND, XTERM_FG( 66), sizeof(XTERM_FG( 66))-1, T(COLOR_FG_5F8787), 3, T("%x<#5F8787>"), 11},
{ CS_FG( 67), CS_FOREGROUND, XTERM_FG( 67), sizeof(XTERM_FG( 67))-1, T(COLOR_FG_5F87AF), 3, T("%x<#5F87AF>"), 11},
{ CS_FG( 68), CS_FOREGROUND, XTERM_FG( 68), sizeof(XTERM_FG( 68))-1, T(COLOR_FG_5F87D7), 3, T("%x<#5F87D7>"), 11},
{ CS_FG( 69), CS_FOREGROUND, XTERM_FG( 69), sizeof(XTERM_FG( 69))-1, T(COLOR_FG_5F87FF), 3, T("%x<#5F87FF>"), 11},
{ CS_FG( 70), CS_FOREGROUND, XTERM_FG( 70), sizeof(XTERM_FG( 70))-1, T(COLOR_FG_5FAF00), 3, T("%x<#5FAF00>"), 11},
{ CS_FG( 71), CS_FOREGROUND, XTERM_FG( 71), sizeof(XTERM_FG( 71))-1, T(COLOR_FG_5FAF5F), 3, T("%x<#5FAF5F>"), 11},
{ CS_FG( 72), CS_FOREGROUND, XTERM_FG( 72), sizeof(XTERM_FG( 72))-1, T(COLOR_FG_5FAF87), 3, T("%x<#5FAF87>"), 11},
{ CS_FG( 73), CS_FOREGROUND, XTERM_FG( 73), sizeof(XTERM_FG( 73))-1, T(COLOR_FG_5FAFAF), 3, T("%x<#5FAFAF>"), 11},
{ CS_FG( 74), CS_FOREGROUND, XTERM_FG( 74), sizeof(XTERM_FG( 74))-1, T(COLOR_FG_5FAFD7), 3, T("%x<#5FAFD7>"), 11},
{ CS_FG( 75), CS_FOREGROUND, XTERM_FG( 75), sizeof(XTERM_FG( 75))-1, T(COLOR_FG_5FAFFF), 3, T("%x<#5FAFFF>"), 11},
{ CS_FG( 76), CS_FOREGROUND, XTERM_FG( 76), sizeof(XTERM_FG( 76))-1, T(COLOR_FG_5FD700), 3, T("%x<#5FD700>"), 11},
{ CS_FG( 77), CS_FOREGROUND, XTERM_FG( 77), sizeof(XTERM_FG( 77))-1, T(COLOR_FG_5FD75F), 3, T("%x<#5FD75F>"), 11},
{ CS_FG( 78), CS_FOREGROUND, XTERM_FG( 78), sizeof(XTERM_FG( 78))-1, T(COLOR_FG_5FD787), 3, T("%x<#5FD787>"), 11},
{ CS_FG( 79), CS_FOREGROUND, XTERM_FG( 79), sizeof(XTERM_FG( 79))-1, T(COLOR_FG_5FD7AF), 3, T("%x<#5FD7AF>"), 11},
{ CS_FG( 80), CS_FOREGROUND, XTERM_FG( 80), sizeof(XTERM_FG( 80))-1, T(COLOR_FG_5FD7D7), 3, T("%x<#5FD7D7>"), 11},
{ CS_FG( 81), CS_FOREGROUND, XTERM_FG( 81), sizeof(XTERM_FG( 81))-1, T(COLOR_FG_5FD7FF), 3, T("%x<#5FD7FF>"), 11},
{ CS_FG( 82), CS_FOREGROUND, XTERM_FG( 82), sizeof(XTERM_FG( 82))-1, T(COLOR_FG_5FFF00), 3, T("%x<#5FFF00>"), 11},
{ CS_FG( 83), CS_FOREGROUND, XTERM_FG( 83), sizeof(XTERM_FG( 83))-1, T(COLOR_FG_5FFF5F), 3, T("%x<#5FFF5F>"), 11},
{ CS_FG( 84), CS_FOREGROUND, XTERM_FG( 84), sizeof(XTERM_FG( 84))-1, T(COLOR_FG_5FFF87), 3, T("%x<#5FFF87>"), 11},
{ CS_FG( 85), CS_FOREGROUND, XTERM_FG( 85), sizeof(XTERM_FG( 85))-1, T(COLOR_FG_5FFFAF), 3, T("%x<#5FFFAF>"), 11},
{ CS_FG( 86), CS_FOREGROUND, XTERM_FG( 86), sizeof(XTERM_FG( 86))-1, T(COLOR_FG_5FFFD7), 3, T("%x<#5FFFD7>"), 11},
{ CS_FG( 87), CS_FOREGROUND, XTERM_FG( 87), sizeof(XTERM_FG( 87))-1, T(COLOR_FG_5FFFFF), 3, T("%x<#5FFFFF>"), 11},
{ CS_FG( 88), CS_FOREGROUND, XTERM_FG( 88), sizeof(XTERM_FG( 88))-1, T(COLOR_FG_870000), 3, T("%x<#870000>"), 11},
{ CS_FG( 89), CS_FOREGROUND, XTERM_FG( 89), sizeof(XTERM_FG( 89))-1, T(COLOR_FG_87005F), 3, T("%x<#87005F>"), 11},
{ CS_FG( 90), CS_FOREGROUND, XTERM_FG( 90), sizeof(XTERM_FG( 90))-1, T(COLOR_FG_870087), 3, T("%x<#870087>"), 11},
{ CS_FG( 91), CS_FOREGROUND, XTERM_FG( 91), sizeof(XTERM_FG( 91))-1, T(COLOR_FG_8700AF), 3, T("%x<#8700AF>"), 11},
{ CS_FG( 92), CS_FOREGROUND, XTERM_FG( 92), sizeof(XTERM_FG( 92))-1, T(COLOR_FG_8700D7), 3, T("%x<#8700D7>"), 11},
{ CS_FG( 93), CS_FOREGROUND, XTERM_FG( 93), sizeof(XTERM_FG( 93))-1, T(COLOR_FG_8700FF), 3, T("%x<#8700FF>"), 11},
{ CS_FG( 94), CS_FOREGROUND, XTERM_FG( 94), sizeof(XTERM_FG( 94))-1, T(COLOR_FG_875F00), 3, T("%x<#875F00>"), 11},
{ CS_FG( 95), CS_FOREGROUND, XTERM_FG( 95), sizeof(XTERM_FG( 95))-1, T(COLOR_FG_875F5F), 3, T("%x<#875F5F>"), 11},
{ CS_FG( 96), CS_FOREGROUND, XTERM_FG( 96), sizeof(XTERM_FG( 96))-1, T(COLOR_FG_875F87), 3, T("%x<#875F87>"), 11},
{ CS_FG( 97), CS_FOREGROUND, XTERM_FG( 97), sizeof(XTERM_FG( 97))-1, T(COLOR_FG_875FAF), 3, T("%x<#875FAF>"), 11},
{ CS_FG( 98), CS_FOREGROUND, XTERM_FG( 98), sizeof(XTERM_FG( 98))-1, T(COLOR_FG_875FD7), 3, T("%x<#875FD7>"), 11},
{ CS_FG( 99), CS_FOREGROUND, XTERM_FG( 99), sizeof(XTERM_FG( 99))-1, T(COLOR_FG_875FFF), 3, T("%x<#875FFF>"), 11},
{ CS_FG(100), CS_FOREGROUND, XTERM_FG(100), sizeof(XTERM_FG(100))-1, T(COLOR_FG_878700), 3, T("%x<#878700>"), 11},
{ CS_FG(101), CS_FOREGROUND, XTERM_FG(101), sizeof(XTERM_FG(101))-1, T(COLOR_FG_87875F), 3, T("%x<#87875F>"), 11},
{ CS_FG(102), CS_FOREGROUND, XTERM_FG(102), sizeof(XTERM_FG(102))-1, T(COLOR_FG_878787), 3, T("%x<#878787>"), 11},
{ CS_FG(103), CS_FOREGROUND, XTERM_FG(103), sizeof(XTERM_FG(103))-1, T(COLOR_FG_8787AF), 3, T("%x<#8787AF>"), 11},
{ CS_FG(104), CS_FOREGROUND, XTERM_FG(104), sizeof(XTERM_FG(104))-1, T(COLOR_FG_8787D7), 3, T("%x<#8787D7>"), 11},
{ CS_FG(105), CS_FOREGROUND, XTERM_FG(105), sizeof(XTERM_FG(105))-1, T(COLOR_FG_8787FF), 3, T("%x<#8787FF>"), 11},
{ CS_FG(106), CS_FOREGROUND, XTERM_FG(106), sizeof(XTERM_FG(106))-1, T(COLOR_FG_87AF00), 3, T("%x<#87AF00>"), 11},
{ CS_FG(107), CS_FOREGROUND, XTERM_FG(107), sizeof(XTERM_FG(107))-1, T(COLOR_FG_87AF5F), 3, T("%x<#87AF5F>"), 11},
{ CS_FG(108), CS_FOREGROUND, XTERM_FG(108), sizeof(XTERM_FG(108))-1, T(COLOR_FG_87AF87), 3, T("%x<#87AF87>"), 11},
{ CS_FG(109), CS_FOREGROUND, XTERM_FG(109), sizeof(XTERM_FG(109))-1, T(COLOR_FG_87AFAF), 3, T("%x<#87AFAF>"), 11},
{ CS_FG(110), CS_FOREGROUND, XTERM_FG(110), sizeof(XTERM_FG(110))-1, T(COLOR_FG_87AFD7), 3, T("%x<#87AFD7>"), 11},
{ CS_FG(111), CS_FOREGROUND, XTERM_FG(111), sizeof(XTERM_FG(111))-1, T(COLOR_FG_87AFFF), 3, T("%x<#87AFFF>"), 11},
{ CS_FG(112), CS_FOREGROUND, XTERM_FG(112), sizeof(XTERM_FG(112))-1, T(COLOR_FG_87D700), 3, T("%x<#87D700>"), 11},
{ CS_FG(113), CS_FOREGROUND, XTERM_FG(113), sizeof(XTERM_FG(113))-1, T(COLOR_FG_87D75A), 3, T("%x<#87D75A>"), 11},
{ CS_FG(114), CS_FOREGROUND, XTERM_FG(114), sizeof(XTERM_FG(114))-1, T(COLOR_FG_87D787), 3, T("%x<#87D787>"), 11},
{ CS_FG(115), CS_FOREGROUND, XTERM_FG(115), sizeof(XTERM_FG(115))-1, T(COLOR_FG_87D7AF), 3, T("%x<#87D7AF>"), 11},
{ CS_FG(116), CS_FOREGROUND, XTERM_FG(116), sizeof(XTERM_FG(116))-1, T(COLOR_FG_87D7D7), 3, T("%x<#87D7D7>"), 11},
{ CS_FG(117), CS_FOREGROUND, XTERM_FG(117), sizeof(XTERM_FG(117))-1, T(COLOR_FG_87D7FF), 3, T("%x<#87D7FF>"), 11},
{ CS_FG(118), CS_FOREGROUND, XTERM_FG(118), sizeof(XTERM_FG(118))-1, T(COLOR_FG_87FF00), 3, T("%x<#87FF00>"), 11},
{ CS_FG(119), CS_FOREGROUND, XTERM_FG(119), sizeof(XTERM_FG(119))-1, T(COLOR_FG_87FF5F), 3, T("%x<#87FF5F>"), 11},
{ CS_FG(120), CS_FOREGROUND, XTERM_FG(120), sizeof(XTERM_FG(120))-1, T(COLOR_FG_87FF87), 3, T("%x<#87FF87>"), 11},
{ CS_FG(121), CS_FOREGROUND, XTERM_FG(121), sizeof(XTERM_FG(121))-1, T(COLOR_FG_87FFAF), 3, T("%x<#87FFAF>"), 11},
{ CS_FG(122), CS_FOREGROUND, XTERM_FG(122), sizeof(XTERM_FG(122))-1, T(COLOR_FG_87FFD7), 3, T("%x<#87FFD7>"), 11},
{ CS_FG(123), CS_FOREGROUND, XTERM_FG(123), sizeof(XTERM_FG(123))-1, T(COLOR_FG_87FFFF), 3, T("%x<#87FFFF>"), 11},
{ CS_FG(124), CS_FOREGROUND, XTERM_FG(124), sizeof(XTERM_FG(124))-1, T(COLOR_FG_AF0000), 3, T("%x<#AF0000>"), 11},
{ CS_FG(125), CS_FOREGROUND, XTERM_FG(125), sizeof(XTERM_FG(125))-1, T(COLOR_FG_AF005F), 3, T("%x<#AF005F>"), 11},
{ CS_FG(126), CS_FOREGROUND, XTERM_FG(126), sizeof(XTERM_FG(126))-1, T(COLOR_FG_AF0087), 3, T("%x<#AF0087>"), 11},
{ CS_FG(127), CS_FOREGROUND, XTERM_FG(127), sizeof(XTERM_FG(127))-1, T(COLOR_FG_AF00AF), 3, T("%x<#AF00AF>"), 11},
{ CS_FG(128), CS_FOREGROUND, XTERM_FG(128), sizeof(XTERM_FG(128))-1, T(COLOR_FG_AF00D7), 3, T("%x<#AF00D7>"), 11},
{ CS_FG(129), CS_FOREGROUND, XTERM_FG(129), sizeof(XTERM_FG(129))-1, T(COLOR_FG_AF00FF), 3, T("%x<#AF00FF>"), 11},
{ CS_FG(130), CS_FOREGROUND, XTERM_FG(130), sizeof(XTERM_FG(130))-1, T(COLOR_FG_AF5F00), 3, T("%x<#AF5F00>"), 11},
{ CS_FG(131), CS_FOREGROUND, XTERM_FG(131), sizeof(XTERM_FG(131))-1, T(COLOR_FG_AF5F5F), 3, T("%x<#AF5F5F>"), 11},
{ CS_FG(132), CS_FOREGROUND, XTERM_FG(132), sizeof(XTERM_FG(132))-1, T(COLOR_FG_AF5F87), 3, T("%x<#AF5F87>"), 11},
{ CS_FG(133), CS_FOREGROUND, XTERM_FG(133), sizeof(XTERM_FG(133))-1, T(COLOR_FG_AF5FAF), 3, T("%x<#AF5FAF>"), 11},
{ CS_FG(134), CS_FOREGROUND, XTERM_FG(134), sizeof(XTERM_FG(134))-1, T(COLOR_FG_AF5FD7), 3, T("%x<#AF5FD7>"), 11},
{ CS_FG(135), CS_FOREGROUND, XTERM_FG(135), sizeof(XTERM_FG(135))-1, T(COLOR_FG_AF5FFF), 3, T("%x<#AF5FFF>"), 11},
{ CS_FG(136), CS_FOREGROUND, XTERM_FG(136), sizeof(XTERM_FG(136))-1, T(COLOR_FG_AF8700), 3, T("%x<#AF8700>"), 11},
{ CS_FG(137), CS_FOREGROUND, XTERM_FG(137), sizeof(XTERM_FG(137))-1, T(COLOR_FG_AF875F), 3, T("%x<#AF875F>"), 11},
{ CS_FG(138), CS_FOREGROUND, XTERM_FG(138), sizeof(XTERM_FG(138))-1, T(COLOR_FG_AF8787), 3, T("%x<#AF8787>"), 11},
{ CS_FG(139), CS_FOREGROUND, XTERM_FG(139), sizeof(XTERM_FG(139))-1, T(COLOR_FG_AF87AF), 3, T("%x<#AF87AF>"), 11},
{ CS_FG(140), CS_FOREGROUND, XTERM_FG(140), sizeof(XTERM_FG(140))-1, T(COLOR_FG_AF87D7), 3, T("%x<#AF87D7>"), 11},
{ CS_FG(141), CS_FOREGROUND, XTERM_FG(141), sizeof(XTERM_FG(141))-1, T(COLOR_FG_AF87FF), 3, T("%x<#AF87FF>"), 11},
{ CS_FG(142), CS_FOREGROUND, XTERM_FG(142), sizeof(XTERM_FG(142))-1, T(COLOR_FG_AFAF00), 3, T("%x<#AFAF00>"), 11},
{ CS_FG(143), CS_FOREGROUND, XTERM_FG(143), sizeof(XTERM_FG(143))-1, T(COLOR_FG_AFAF5F), 3, T("%x<#AFAF5F>"), 11},
{ CS_FG(144), CS_FOREGROUND, XTERM_FG(144), sizeof(XTERM_FG(144))-1, T(COLOR_FG_AFAF87), 3, T("%x<#AFAF87>"), 11},
{ CS_FG(145), CS_FOREGROUND, XTERM_FG(145), sizeof(XTERM_FG(145))-1, T(COLOR_FG_AFAFAF), 3, T("%x<#AFAFAF>"), 11},
{ CS_FG(146), CS_FOREGROUND, XTERM_FG(146), sizeof(XTERM_FG(146))-1, T(COLOR_FG_AFAFD7), 3, T("%x<#AFAFD7>"), 11},
{ CS_FG(147), CS_FOREGROUND, XTERM_FG(147), sizeof(XTERM_FG(147))-1, T(COLOR_FG_AFAFFF), 3, T("%x<#AFAFFF>"), 11},
{ CS_FG(148), CS_FOREGROUND, XTERM_FG(148), sizeof(XTERM_FG(148))-1, T(COLOR_FG_AFD700), 3, T("%x<#AFD700>"), 11},
{ CS_FG(149), CS_FOREGROUND, XTERM_FG(149), sizeof(XTERM_FG(149))-1, T(COLOR_FG_AFD75F), 3, T("%x<#AFD75F>"), 11},
{ CS_FG(150), CS_FOREGROUND, XTERM_FG(150), sizeof(XTERM_FG(150))-1, T(COLOR_FG_AFD787), 3, T("%x<#AFD787>"), 11},
{ CS_FG(151), CS_FOREGROUND, XTERM_FG(151), sizeof(XTERM_FG(151))-1, T(COLOR_FG_AFD7AF), 3, T("%x<#AFD7AF>"), 11},
{ CS_FG(152), CS_FOREGROUND, XTERM_FG(152), sizeof(XTERM_FG(152))-1, T(COLOR_FG_AFD7D7), 3, T("%x<#AFD7D7>"), 11},
{ CS_FG(153), CS_FOREGROUND, XTERM_FG(153), sizeof(XTERM_FG(153))-1, T(COLOR_FG_AFD7FF), 3, T("%x<#AFD7FF>"), 11},
{ CS_FG(154), CS_FOREGROUND, XTERM_FG(154), sizeof(XTERM_FG(154))-1, T(COLOR_FG_AFFF00), 3, T("%x<#AFFF00>"), 11},
{ CS_FG(155), CS_FOREGROUND, XTERM_FG(155), sizeof(XTERM_FG(155))-1, T(COLOR_FG_AFFF5F), 3, T("%x<#AFFF5F>"), 11},
{ CS_FG(156), CS_FOREGROUND, XTERM_FG(156), sizeof(XTERM_FG(156))-1, T(COLOR_FG_AFFF87), 3, T("%x<#AFFF87>"), 11},
{ CS_FG(157), CS_FOREGROUND, XTERM_FG(157), sizeof(XTERM_FG(157))-1, T(COLOR_FG_AFFFAF), 3, T("%x<#AFFFAF>"), 11},
{ CS_FG(158), CS_FOREGROUND, XTERM_FG(158), sizeof(XTERM_FG(158))-1, T(COLOR_FG_AFFFD7), 3, T("%x<#AFFFD7>"), 11},
{ CS_FG(159), CS_FOREGROUND, XTERM_FG(159), sizeof(XTERM_FG(159))-1, T(COLOR_FG_AFFFFF), 3, T("%x<#AFFFFF>"), 11},
{ CS_FG(160), CS_FOREGROUND, XTERM_FG(160), sizeof(XTERM_FG(160))-1, T(COLOR_FG_D70000), 3, T("%x<#D70000>"), 11},
{ CS_FG(161), CS_FOREGROUND, XTERM_FG(161), sizeof(XTERM_FG(161))-1, T(COLOR_FG_D7005F), 3, T("%x<#D7005F>"), 11},
{ CS_FG(162), CS_FOREGROUND, XTERM_FG(162), sizeof(XTERM_FG(162))-1, T(COLOR_FG_D70087), 3, T("%x<#D70087>"), 11},
{ CS_FG(163), CS_FOREGROUND, XTERM_FG(163), sizeof(XTERM_FG(163))-1, T(COLOR_FG_D700AF), 3, T("%x<#D700AF>"), 11},
{ CS_FG(164), CS_FOREGROUND, XTERM_FG(164), sizeof(XTERM_FG(164))-1, T(COLOR_FG_D700D7), 3, T("%x<#D700D7>"), 11},
{ CS_FG(165), CS_FOREGROUND, XTERM_FG(165), sizeof(XTERM_FG(165))-1, T(COLOR_FG_D700FF), 3, T("%x<#D700FF>"), 11},
{ CS_FG(166), CS_FOREGROUND, XTERM_FG(166), sizeof(XTERM_FG(166))-1, T(COLOR_FG_D75F00), 3, T("%x<#D75F00>"), 11},
{ CS_FG(167), CS_FOREGROUND, XTERM_FG(167), sizeof(XTERM_FG(167))-1, T(COLOR_FG_D75F5F), 3, T("%x<#D75F5F>"), 11},
{ CS_FG(168), CS_FOREGROUND, XTERM_FG(168), sizeof(XTERM_FG(168))-1, T(COLOR_FG_D75F87), 3, T("%x<#D75F87>"), 11},
{ CS_FG(169), CS_FOREGROUND, XTERM_FG(169), sizeof(XTERM_FG(169))-1, T(COLOR_FG_D75FAF), 3, T("%x<#D75FAF>"), 11},
{ CS_FG(170), CS_FOREGROUND, XTERM_FG(170), sizeof(XTERM_FG(170))-1, T(COLOR_FG_D75FD7), 3, T("%x<#D75FD7>"), 11},
{ CS_FG(171), CS_FOREGROUND, XTERM_FG(171), sizeof(XTERM_FG(171))-1, T(COLOR_FG_D75FFF), 3, T("%x<#D75FFF>"), 11},
{ CS_FG(172), CS_FOREGROUND, XTERM_FG(172), sizeof(XTERM_FG(172))-1, T(COLOR_FG_D78700), 3, T("%x<#D78700>"), 11},
{ CS_FG(173), CS_FOREGROUND, XTERM_FG(173), sizeof(XTERM_FG(173))-1, T(COLOR_FG_D7875A), 3, T("%x<#D7875A>"), 11},
{ CS_FG(174), CS_FOREGROUND, XTERM_FG(174), sizeof(XTERM_FG(174))-1, T(COLOR_FG_D78787), 3, T("%x<#D78787>"), 11},
{ CS_FG(175), CS_FOREGROUND, XTERM_FG(175), sizeof(XTERM_FG(175))-1, T(COLOR_FG_D787AF), 3, T("%x<#D787AF>"), 11},
{ CS_FG(176), CS_FOREGROUND, XTERM_FG(176), sizeof(XTERM_FG(176))-1, T(COLOR_FG_D787D7), 3, T("%x<#D787D7>"), 11},
{ CS_FG(177), CS_FOREGROUND, XTERM_FG(177), sizeof(XTERM_FG(177))-1, T(COLOR_FG_D787FF), 3, T("%x<#D787FF>"), 11},
{ CS_FG(178), CS_FOREGROUND, XTERM_FG(178), sizeof(XTERM_FG(178))-1, T(COLOR_FG_D7AF00), 3, T("%x<#D7AF00>"), 11},
{ CS_FG(179), CS_FOREGROUND, XTERM_FG(179), sizeof(XTERM_FG(179))-1, T(COLOR_FG_D7AF5A), 3, T("%x<#D7AF5A>"), 11},
{ CS_FG(180), CS_FOREGROUND, XTERM_FG(180), sizeof(XTERM_FG(180))-1, T(COLOR_FG_D7AF87), 3, T("%x<#D7AF87>"), 11},
{ CS_FG(181), CS_FOREGROUND, XTERM_FG(181), sizeof(XTERM_FG(181))-1, T(COLOR_FG_D7AFAF), 3, T("%x<#D7AFAF>"), 11},
{ CS_FG(182), CS_FOREGROUND, XTERM_FG(182), sizeof(XTERM_FG(182))-1, T(COLOR_FG_D7AFD7), 3, T("%x<#D7AFD7>"), 11},
{ CS_FG(183), CS_FOREGROUND, XTERM_FG(183), sizeof(XTERM_FG(183))-1, T(COLOR_FG_D7AFFF), 3, T("%x<#D7AFFF>"), 11},
{ CS_FG(184), CS_FOREGROUND, XTERM_FG(184), sizeof(XTERM_FG(184))-1, T(COLOR_FG_D7D700), 3, T("%x<#D7D700>"), 11},
{ CS_FG(185), CS_FOREGROUND, XTERM_FG(185), sizeof(XTERM_FG(185))-1, T(COLOR_FG_D7D75F), 3, T("%x<#D7D75F>"), 11},
{ CS_FG(186), CS_FOREGROUND, XTERM_FG(186), sizeof(XTERM_FG(186))-1, T(COLOR_FG_D7D787), 3, T("%x<#D7D787>"), 11},
{ CS_FG(187), CS_FOREGROUND, XTERM_FG(187), sizeof(XTERM_FG(187))-1, T(COLOR_FG_D7D7AF), 3, T("%x<#D7D7AF>"), 11},
{ CS_FG(188), CS_FOREGROUND, XTERM_FG(188), sizeof(XTERM_FG(188))-1, T(COLOR_FG_D7D7D7), 3, T("%x<#D7D7D7>"), 11},
{ CS_FG(189), CS_FOREGROUND, XTERM_FG(189), sizeof(XTERM_FG(189))-1, T(COLOR_FG_D7D7FF), 3, T("%x<#D7D7FF>"), 11},
{ CS_FG(190), CS_FOREGROUND, XTERM_FG(190), sizeof(XTERM_FG(190))-1, T(COLOR_FG_D7FF00), 3, T("%x<#D7FF00>"), 11},
{ CS_FG(191), CS_FOREGROUND, XTERM_FG(191), sizeof(XTERM_FG(191))-1, T(COLOR_FG_D7FF5F), 3, T("%x<#D7FF5F>"), 11},
{ CS_FG(192), CS_FOREGROUND, XTERM_FG(192), sizeof(XTERM_FG(192))-1, T(COLOR_FG_D7FF87), 3, T("%x<#D7FF87>"), 11},
{ CS_FG(193), CS_FOREGROUND, XTERM_FG(193), sizeof(XTERM_FG(193))-1, T(COLOR_FG_D7FFAF), 3, T("%x<#D7FFAF>"), 11},
{ CS_FG(194), CS_FOREGROUND, XTERM_FG(194), sizeof(XTERM_FG(194))-1, T(COLOR_FG_D7FFD7), 3, T("%x<#D7FFD7>"), 11},
{ CS_FG(195), CS_FOREGROUND, XTERM_FG(195), sizeof(XTERM_FG(195))-1, T(COLOR_FG_D7FFFF), 3, T("%x<#D7FFFF>"), 11},
{ CS_FG(196), CS_FOREGROUND, XTERM_FG(196), sizeof(XTERM_FG(196))-1, T(COLOR_FG_FF0000), 3, T("%x<#FF0000>"), 11},
{ CS_FG(197), CS_FOREGROUND, XTERM_FG(197), sizeof(XTERM_FG(197))-1, T(COLOR_FG_FF005F), 3, T("%x<#FF005F>"), 11},
{ CS_FG(198), CS_FOREGROUND, XTERM_FG(198), sizeof(XTERM_FG(198))-1, T(COLOR_FG_FF0087), 3, T("%x<#FF0087>"), 11},
{ CS_FG(199), CS_FOREGROUND, XTERM_FG(199), sizeof(XTERM_FG(199))-1, T(COLOR_FG_FF00AF), 3, T("%x<#FF00AF>"), 11},
{ CS_FG(200), CS_FOREGROUND, XTERM_FG(200), sizeof(XTERM_FG(200))-1, T(COLOR_FG_FF00D7), 3, T("%x<#FF00D7>"), 11},
{ CS_FG(201), CS_FOREGROUND, XTERM_FG(201), sizeof(XTERM_FG(201))-1, T(COLOR_FG_FF00FF), 3, T("%x<#FF00FF>"), 11},
{ CS_FG(202), CS_FOREGROUND, XTERM_FG(202), sizeof(XTERM_FG(202))-1, T(COLOR_FG_FF5F00), 3, T("%x<#FF5F00>"), 11},
{ CS_FG(203), CS_FOREGROUND, XTERM_FG(203), sizeof(XTERM_FG(203))-1, T(COLOR_FG_FF5F5F), 3, T("%x<#FF5F5F>"), 11},
{ CS_FG(204), CS_FOREGROUND, XTERM_FG(204), sizeof(XTERM_FG(204))-1, T(COLOR_FG_FF5F87), 3, T("%x<#FF5F87>"), 11},
{ CS_FG(205), CS_FOREGROUND, XTERM_FG(205), sizeof(XTERM_FG(205))-1, T(COLOR_FG_FF5FAF), 3, T("%x<#FF5FAF>"), 11},
{ CS_FG(206), CS_FOREGROUND, XTERM_FG(206), sizeof(XTERM_FG(206))-1, T(COLOR_FG_FF5FD7), 3, T("%x<#FF5FD7>"), 11},
{ CS_FG(207), CS_FOREGROUND, XTERM_FG(207), sizeof(XTERM_FG(207))-1, T(COLOR_FG_FF5FFF), 3, T("%x<#FF5FFF>"), 11},
{ CS_FG(208), CS_FOREGROUND, XTERM_FG(208), sizeof(XTERM_FG(208))-1, T(COLOR_FG_FF8700), 3, T("%x<#FF8700>"), 11},
{ CS_FG(209), CS_FOREGROUND, XTERM_FG(209), sizeof(XTERM_FG(209))-1, T(COLOR_FG_FF875F), 3, T("%x<#FF875F>"), 11},
{ CS_FG(210), CS_FOREGROUND, XTERM_FG(210), sizeof(XTERM_FG(210))-1, T(COLOR_FG_FF8787), 3, T("%x<#FF8787>"), 11},
{ CS_FG(211), CS_FOREGROUND, XTERM_FG(211), sizeof(XTERM_FG(211))-1, T(COLOR_FG_FF87AF), 3, T("%x<#FF87AF>"), 11},
{ CS_FG(212), CS_FOREGROUND, XTERM_FG(212), sizeof(XTERM_FG(212))-1, T(COLOR_FG_FF87D7), 3, T("%x<#FF87D7>"), 11},
{ CS_FG(213), CS_FOREGROUND, XTERM_FG(213), sizeof(XTERM_FG(213))-1, T(COLOR_FG_FF87FF), 3, T("%x<#FF87FF>"), 11},
{ CS_FG(214), CS_FOREGROUND, XTERM_FG(214), sizeof(XTERM_FG(214))-1, T(COLOR_FG_FFAF00), 3, T("%x<#FFAF00>"), 11},
{ CS_FG(215), CS_FOREGROUND, XTERM_FG(215), sizeof(XTERM_FG(215))-1, T(COLOR_FG_FFAF5F), 3, T("%x<#FFAF5F>"), 11},
{ CS_FG(216), CS_FOREGROUND, XTERM_FG(216), sizeof(XTERM_FG(216))-1, T(COLOR_FG_FFAF87), 3, T("%x<#FFAF87>"), 11},
{ CS_FG(217), CS_FOREGROUND, XTERM_FG(217), sizeof(XTERM_FG(217))-1, T(COLOR_FG_FFAFAF), 3, T("%x<#FFAFAF>"), 11},
{ CS_FG(218), CS_FOREGROUND, XTERM_FG(218), sizeof(XTERM_FG(218))-1, T(COLOR_FG_FFAFD7), 3, T("%x<#FFAFD7>"), 11},
{ CS_FG(219), CS_FOREGROUND, XTERM_FG(219), sizeof(XTERM_FG(219))-1, T(COLOR_FG_FFAFFF), 3, T("%x<#FFAFFF>"), 11},
{ CS_FG(220), CS_FOREGROUND, XTERM_FG(220), sizeof(XTERM_FG(220))-1, T(COLOR_FG_FFD700), 3, T("%x<#FFD700>"), 11},
{ CS_FG(221), CS_FOREGROUND, XTERM_FG(221), sizeof(XTERM_FG(221))-1, T(COLOR_FG_FFD75F), 3, T("%x<#FFD75F>"), 11},
{ CS_FG(222), CS_FOREGROUND, XTERM_FG(222), sizeof(XTERM_FG(222))-1, T(COLOR_FG_FFD787), 3, T("%x<#FFD787>"), 11},
{ CS_FG(223), CS_FOREGROUND, XTERM_FG(223), sizeof(XTERM_FG(223))-1, T(COLOR_FG_FFD7AF), 3, T("%x<#FFD7AF>"), 11},
{ CS_FG(224), CS_FOREGROUND, XTERM_FG(224), sizeof(XTERM_FG(224))-1, T(COLOR_FG_FFD7D7), 3, T("%x<#FFD7D7>"), 11},
{ CS_FG(225), CS_FOREGROUND, XTERM_FG(225), sizeof(XTERM_FG(225))-1, T(COLOR_FG_FFD7FF), 3, T("%x<#FFD7FF>"), 11},
{ CS_FG(226), CS_FOREGROUND, XTERM_FG(226), sizeof(XTERM_FG(226))-1, T(COLOR_FG_FFFF00), 3, T("%x<#FFFF00>"), 11},
{ CS_FG(227), CS_FOREGROUND, XTERM_FG(227), sizeof(XTERM_FG(227))-1, T(COLOR_FG_FFFF5F), 3, T("%x<#FFFF5F>"), 11},
{ CS_FG(228), CS_FOREGROUND, XTERM_FG(228), sizeof(XTERM_FG(228))-1, T(COLOR_FG_FFFF87), 3, T("%x<#FFFF87>"), 11},
{ CS_FG(229), CS_FOREGROUND, XTERM_FG(229), sizeof(XTERM_FG(229))-1, T(COLOR_FG_FFFFAF), 3, T("%x<#FFFFAF>"), 11},
{ CS_FG(230), CS_FOREGROUND, XTERM_FG(230), sizeof(XTERM_FG(230))-1, T(COLOR_FG_FFFFD7), 3, T("%x<#FFFFD7>"), 11},
{ CS_FG(231), CS_FOREGROUND, XTERM_FG(231), sizeof(XTERM_FG(231))-1, T(COLOR_FG_FFFFFF_2),3, T("%x<#FFFFFF>"), 11},
{ CS_FG(232), CS_FOREGROUND, XTERM_FG(232), sizeof(XTERM_FG(232))-1, T(COLOR_FG_080808), 3, T("%x<#080808>"), 11},
{ CS_FG(233), CS_FOREGROUND, XTERM_FG(233), sizeof(XTERM_FG(233))-1, T(COLOR_FG_121212), 3, T("%x<#121212>"), 11},
{ CS_FG(234), CS_FOREGROUND, XTERM_FG(234), sizeof(XTERM_FG(234))-1, T(COLOR_FG_1C1C1C), 3, T("%x<#1C1C1C>"), 11},
{ CS_FG(235), CS_FOREGROUND, XTERM_FG(235), sizeof(XTERM_FG(235))-1, T(COLOR_FG_262626), 3, T("%x<#262626>"), 11},
{ CS_FG(236), CS_FOREGROUND, XTERM_FG(236), sizeof(XTERM_FG(236))-1, T(COLOR_FG_303030), 3, T("%x<#303030>"), 11},
{ CS_FG(237), CS_FOREGROUND, XTERM_FG(237), sizeof(XTERM_FG(237))-1, T(COLOR_FG_3A3A3A), 3, T("%x<#3A3A3A>"), 11},
{ CS_FG(238), CS_FOREGROUND, XTERM_FG(238), sizeof(XTERM_FG(238))-1, T(COLOR_FG_444444), 3, T("%x<#444444>"), 11},
{ CS_FG(239), CS_FOREGROUND, XTERM_FG(239), sizeof(XTERM_FG(239))-1, T(COLOR_FG_4E4E4E), 3, T("%x<#4E4E4E>"), 11},
{ CS_FG(240), CS_FOREGROUND, XTERM_FG(240), sizeof(XTERM_FG(240))-1, T(COLOR_FG_585858), 3, T("%x<#585858>"), 11},
{ CS_FG(241), CS_FOREGROUND, XTERM_FG(241), sizeof(XTERM_FG(241))-1, T(COLOR_FG_626262), 3, T("%x<#626262>"), 11},
{ CS_FG(242), CS_FOREGROUND, XTERM_FG(242), sizeof(XTERM_FG(242))-1, T(COLOR_FG_6C6C6C), 3, T("%x<#6C6C6C>"), 11},
{ CS_FG(243), CS_FOREGROUND, XTERM_FG(243), sizeof(XTERM_FG(243))-1, T(COLOR_FG_767676), 3, T("%x<#767676>"), 11},
{ CS_FG(244), CS_FOREGROUND, XTERM_FG(244), sizeof(XTERM_FG(244))-1, T(COLOR_FG_808080), 3, T("%x<#808080>"), 11},
{ CS_FG(245), CS_FOREGROUND, XTERM_FG(245), sizeof(XTERM_FG(245))-1, T(COLOR_FG_8A8A8A), 3, T("%x<#8A8A8A>"), 11},
{ CS_FG(246), CS_FOREGROUND, XTERM_FG(246), sizeof(XTERM_FG(246))-1, T(COLOR_FG_949494), 3, T("%x<#949494>"), 11},
{ CS_FG(247), CS_FOREGROUND, XTERM_FG(247), sizeof(XTERM_FG(247))-1, T(COLOR_FG_9E9E9E), 3, T("%x<#9E9E9E>"), 11},
{ CS_FG(248), CS_FOREGROUND, XTERM_FG(248), sizeof(XTERM_FG(248))-1, T(COLOR_FG_A8A8A8), 3, T("%x<#A8A8A8>"), 11},
{ CS_FG(249), CS_FOREGROUND, XTERM_FG(249), sizeof(XTERM_FG(249))-1, T(COLOR_FG_B2B2B2), 3, T("%x<#B2B2B2>"), 11},
{ CS_FG(250), CS_FOREGROUND, XTERM_FG(250), sizeof(XTERM_FG(250))-1, T(COLOR_FG_BCBCBC), 3, T("%x<#BCBCBC>"), 11},
{ CS_FG(251), CS_FOREGROUND, XTERM_FG(251), sizeof(XTERM_FG(251))-1, T(COLOR_FG_C6C6C6), 3, T("%x<#C6C6C6>"), 11},
{ CS_FG(252), CS_FOREGROUND, XTERM_FG(252), sizeof(XTERM_FG(252))-1, T(COLOR_FG_D0D0D0), 3, T("%x<#D0D0D0>"), 11},
{ CS_FG(253), CS_FOREGROUND, XTERM_FG(253), sizeof(XTERM_FG(253))-1, T(COLOR_FG_DADADA), 3, T("%x<#DADADA>"), 11},
{ CS_FG(254), CS_FOREGROUND, XTERM_FG(254), sizeof(XTERM_FG(254))-1, T(COLOR_FG_E4E4E4), 3, T("%x<#E4E4E4>"), 11},
{ CS_FG(255), CS_FOREGROUND, XTERM_FG(255), sizeof(XTERM_FG(255))-1, T(COLOR_FG_EEEEEE), 3, T("%x<#EEEEEE>"), 11},
{ CS_BG_BLACK, CS_BACKGROUND, ANSI_BBLACK, sizeof(ANSI_BBLACK)-1, T(COLOR_BG_BLACK), 3, T("%xX"), 3}, // COLOR_INDEX_BG
{ CS_BG_RED, CS_BACKGROUND, ANSI_BRED, sizeof(ANSI_BRED)-1, T(COLOR_BG_RED), 3, T("%xR"), 3},
{ CS_BG_GREEN, CS_BACKGROUND, ANSI_BGREEN, sizeof(ANSI_BGREEN)-1, T(COLOR_BG_GREEN), 3, T("%xG"), 3},
{ CS_BG_YELLOW, CS_BACKGROUND, ANSI_BYELLOW, sizeof(ANSI_BYELLOW)-1, T(COLOR_BG_YELLOW), 3, T("%xY"), 3},
{ CS_BG_BLUE, CS_BACKGROUND, ANSI_BBLUE, sizeof(ANSI_BBLUE)-1, T(COLOR_BG_BLUE), 3, T("%xB"), 3},
{ CS_BG_MAGENTA, CS_BACKGROUND, ANSI_BMAGENTA, sizeof(ANSI_BMAGENTA)-1, T(COLOR_BG_MAGENTA), 3, T("%xM"), 3},
{ CS_BG_CYAN, CS_BACKGROUND, ANSI_BCYAN, sizeof(ANSI_BCYAN)-1, T(COLOR_BG_CYAN), 3, T("%xC"), 3},
2010-08-11 18:15:00 -07:00
{ CS_BG_WHITE, CS_BACKGROUND, ANSI_BWHITE, sizeof(ANSI_BWHITE)-1, T(COLOR_BG_WHITE), 3, T("%xW"), 3},
{ CS_BG( 8), CS_BACKGROUND, ANSI_BBLACK, sizeof(ANSI_BBLACK)-1, T(COLOR_BG_555555), 3, T("%xX"), 3}, // These eight are never used.
{ CS_BG( 9), CS_BACKGROUND, ANSI_BRED, sizeof(ANSI_BRED)-1, T(COLOR_BG_FF5555), 3, T("%xR"), 3}, // .
{ CS_BG( 10), CS_BACKGROUND, ANSI_BGREEN, sizeof(ANSI_BGREEN)-1, T(COLOR_BG_55FF55), 3, T("%xG"), 3}, // .
{ CS_BG( 11), CS_BACKGROUND, ANSI_BYELLOW, sizeof(ANSI_BYELLOW)-1, T(COLOR_BG_FFFF55), 3, T("%xY"), 3}, // .
{ CS_BG( 12), CS_BACKGROUND, ANSI_BBLUE, sizeof(ANSI_BBLUE)-1, T(COLOR_BG_5555FF), 3, T("%xB"), 3}, // .
{ CS_BG( 13), CS_BACKGROUND, ANSI_BMAGENTA, sizeof(ANSI_BMAGENTA)-1, T(COLOR_BG_FF55FF), 3, T("%xM"), 3}, // .
{ CS_BG( 14), CS_BACKGROUND, ANSI_BCYAN, sizeof(ANSI_BCYAN)-1, T(COLOR_BG_55FFFF), 3, T("%xC"), 3}, // .
2010-08-11 22:07:01 -07:00
{ CS_BG( 15), CS_BACKGROUND, ANSI_BWHITE, sizeof(ANSI_BWHITE)-1, T(COLOR_BG_FFFFFF_1),3, T("%xW"), 3}, // -
{ CS_BG( 16), CS_BACKGROUND, XTERM_BG( 16), sizeof(XTERM_BG( 16))-1, T(COLOR_BG_000000), 3, T("%X<#000000>"), 11},
{ CS_BG( 17), CS_BACKGROUND, XTERM_BG( 17), sizeof(XTERM_BG( 17))-1, T(COLOR_BG_00005F), 3, T("%X<#00005F>"), 11},
{ CS_BG( 18), CS_BACKGROUND, XTERM_BG( 18), sizeof(XTERM_BG( 18))-1, T(COLOR_BG_000087), 3, T("%X<#000087>"), 11},
{ CS_BG( 19), CS_BACKGROUND, XTERM_BG( 19), sizeof(XTERM_BG( 19))-1, T(COLOR_BG_0000AF), 3, T("%X<#0000AF>"), 11},
{ CS_BG( 20), CS_BACKGROUND, XTERM_BG( 20), sizeof(XTERM_BG( 20))-1, T(COLOR_BG_0000D7), 3, T("%X<#0000D7>"), 11},
{ CS_BG( 21), CS_BACKGROUND, XTERM_BG( 21), sizeof(XTERM_BG( 21))-1, T(COLOR_BG_0000FF), 3, T("%X<#0000FF>"), 11},
{ CS_BG( 22), CS_BACKGROUND, XTERM_BG( 22), sizeof(XTERM_BG( 22))-1, T(COLOR_BG_005F00), 3, T("%X<#005F00>"), 11},
{ CS_BG( 23), CS_BACKGROUND, XTERM_BG( 23), sizeof(XTERM_BG( 23))-1, T(COLOR_BG_005F5F), 3, T("%X<#005F5F>"), 11},
{ CS_BG( 24), CS_BACKGROUND, XTERM_BG( 24), sizeof(XTERM_BG( 24))-1, T(COLOR_BG_005F87), 3, T("%X<#005F87>"), 11},
{ CS_BG( 25), CS_BACKGROUND, XTERM_BG( 25), sizeof(XTERM_BG( 25))-1, T(COLOR_BG_005FAF), 3, T("%X<#005FAF>"), 11},
{ CS_BG( 26), CS_BACKGROUND, XTERM_BG( 26), sizeof(XTERM_BG( 26))-1, T(COLOR_BG_005FD7), 3, T("%X<#005FD7>"), 11},
{ CS_BG( 27), CS_BACKGROUND, XTERM_BG( 27), sizeof(XTERM_BG( 27))-1, T(COLOR_BG_005FFF), 3, T("%X<#005FFF>"), 11},
{ CS_BG( 28), CS_BACKGROUND, XTERM_BG( 28), sizeof(XTERM_BG( 28))-1, T(COLOR_BG_008700), 3, T("%X<#008700>"), 11},
{ CS_BG( 29), CS_BACKGROUND, XTERM_BG( 29), sizeof(XTERM_BG( 29))-1, T(COLOR_BG_00875F), 3, T("%X<#00875F>"), 11},
{ CS_BG( 30), CS_BACKGROUND, XTERM_BG( 30), sizeof(XTERM_BG( 30))-1, T(COLOR_BG_008785), 3, T("%X<#008785>"), 11},
{ CS_BG( 31), CS_BACKGROUND, XTERM_BG( 31), sizeof(XTERM_BG( 31))-1, T(COLOR_BG_0087AF), 3, T("%X<#0087AF>"), 11},
{ CS_BG( 32), CS_BACKGROUND, XTERM_BG( 32), sizeof(XTERM_BG( 32))-1, T(COLOR_BG_0087D7), 3, T("%X<#0087D7>"), 11},
{ CS_BG( 33), CS_BACKGROUND, XTERM_BG( 33), sizeof(XTERM_BG( 33))-1, T(COLOR_BG_0087FF), 3, T("%X<#0087FF>"), 11},
{ CS_BG( 34), CS_BACKGROUND, XTERM_BG( 34), sizeof(XTERM_BG( 34))-1, T(COLOR_BG_00AF00), 3, T("%X<#00AF00>"), 11},
{ CS_BG( 35), CS_BACKGROUND, XTERM_BG( 35), sizeof(XTERM_BG( 35))-1, T(COLOR_BG_00AF5F), 3, T("%X<#00AF5F>"), 11},
{ CS_BG( 36), CS_BACKGROUND, XTERM_BG( 36), sizeof(XTERM_BG( 36))-1, T(COLOR_BG_00AF87), 3, T("%X<#00AF87>"), 11},
{ CS_BG( 37), CS_BACKGROUND, XTERM_BG( 37), sizeof(XTERM_BG( 37))-1, T(COLOR_BG_00AFAF), 3, T("%X<#00AFAF>"), 11},
{ CS_BG( 38), CS_BACKGROUND, XTERM_BG( 38), sizeof(XTERM_BG( 38))-1, T(COLOR_BG_00AFD7), 3, T("%X<#00AFD7>"), 11},
{ CS_BG( 39), CS_BACKGROUND, XTERM_BG( 39), sizeof(XTERM_BG( 39))-1, T(COLOR_BG_00AFFF), 3, T("%X<#00AFFF>"), 11},
{ CS_BG( 40), CS_BACKGROUND, XTERM_BG( 40), sizeof(XTERM_BG( 40))-1, T(COLOR_BG_00D700), 3, T("%X<#00D700>"), 11},
{ CS_BG( 41), CS_BACKGROUND, XTERM_BG( 41), sizeof(XTERM_BG( 41))-1, T(COLOR_BG_00D75F), 3, T("%X<#00D75F>"), 11},
{ CS_BG( 42), CS_BACKGROUND, XTERM_BG( 42), sizeof(XTERM_BG( 42))-1, T(COLOR_BG_00D787), 3, T("%X<#00D787>"), 11},
{ CS_BG( 43), CS_BACKGROUND, XTERM_BG( 43), sizeof(XTERM_BG( 43))-1, T(COLOR_BG_00D7AF), 3, T("%X<#00D7AF>"), 11},
{ CS_BG( 44), CS_BACKGROUND, XTERM_BG( 44), sizeof(XTERM_BG( 44))-1, T(COLOR_BG_00D7D7), 3, T("%X<#00D7D7>"), 11},
{ CS_BG( 45), CS_BACKGROUND, XTERM_BG( 45), sizeof(XTERM_BG( 45))-1, T(COLOR_BG_00D7FF), 3, T("%X<#00D7FF>"), 11},
{ CS_BG( 46), CS_BACKGROUND, XTERM_BG( 46), sizeof(XTERM_BG( 46))-1, T(COLOR_BG_00FF00), 3, T("%X<#00FF00>"), 11},
{ CS_BG( 47), CS_BACKGROUND, XTERM_BG( 47), sizeof(XTERM_BG( 47))-1, T(COLOR_BG_00FF5A), 3, T("%X<#00FF5A>"), 11},
{ CS_BG( 48), CS_BACKGROUND, XTERM_BG( 48), sizeof(XTERM_BG( 48))-1, T(COLOR_BG_00FF87), 3, T("%X<#00FF87>"), 11},
{ CS_BG( 49), CS_BACKGROUND, XTERM_BG( 49), sizeof(XTERM_BG( 49))-1, T(COLOR_BG_00FFAF), 3, T("%X<#00FFAF>"), 11},
{ CS_BG( 50), CS_BACKGROUND, XTERM_BG( 50), sizeof(XTERM_BG( 50))-1, T(COLOR_BG_00FFD7), 3, T("%X<#00FFD7>"), 11},
{ CS_BG( 51), CS_BACKGROUND, XTERM_BG( 51), sizeof(XTERM_BG( 51))-1, T(COLOR_BG_00FFFF), 3, T("%X<#00FFFF>"), 11},
{ CS_BG( 52), CS_BACKGROUND, XTERM_BG( 52), sizeof(XTERM_BG( 52))-1, T(COLOR_BG_5F0000), 3, T("%X<#5F0000>"), 11},
{ CS_BG( 53), CS_BACKGROUND, XTERM_BG( 53), sizeof(XTERM_BG( 53))-1, T(COLOR_BG_5F005F), 3, T("%X<#5F005F>"), 11},
{ CS_BG( 54), CS_BACKGROUND, XTERM_BG( 54), sizeof(XTERM_BG( 54))-1, T(COLOR_BG_5F0087), 3, T("%X<#5F0087>"), 11},
{ CS_BG( 55), CS_BACKGROUND, XTERM_BG( 55), sizeof(XTERM_BG( 55))-1, T(COLOR_BG_5F00AF), 3, T("%X<#5F00AF>"), 11},
{ CS_BG( 56), CS_BACKGROUND, XTERM_BG( 56), sizeof(XTERM_BG( 56))-1, T(COLOR_BG_5F00D7), 3, T("%X<#5F00D7>"), 11},
{ CS_BG( 57), CS_BACKGROUND, XTERM_BG( 57), sizeof(XTERM_BG( 57))-1, T(COLOR_BG_5F00FF), 3, T("%X<#5F00FF>"), 11},
{ CS_BG( 58), CS_BACKGROUND, XTERM_BG( 58), sizeof(XTERM_BG( 58))-1, T(COLOR_BG_5F5F00), 3, T("%X<#5F5F00>"), 11},
{ CS_BG( 59), CS_BACKGROUND, XTERM_BG( 59), sizeof(XTERM_BG( 59))-1, T(COLOR_BG_5F5F5F), 3, T("%X<#5F5F5F>"), 11},
{ CS_BG( 60), CS_BACKGROUND, XTERM_BG( 60), sizeof(XTERM_BG( 60))-1, T(COLOR_BG_5F5F87), 3, T("%X<#5F5F87>"), 11},
{ CS_BG( 61), CS_BACKGROUND, XTERM_BG( 61), sizeof(XTERM_BG( 61))-1, T(COLOR_BG_5F5FAF), 3, T("%X<#5F5FAF>"), 11},
{ CS_BG( 62), CS_BACKGROUND, XTERM_BG( 62), sizeof(XTERM_BG( 62))-1, T(COLOR_BG_5F5FD7), 3, T("%X<#5F5FD7>"), 11},
{ CS_BG( 63), CS_BACKGROUND, XTERM_BG( 63), sizeof(XTERM_BG( 63))-1, T(COLOR_BG_5F5FFF), 3, T("%X<#5F5FFF>"), 11},
{ CS_BG( 64), CS_BACKGROUND, XTERM_BG( 64), sizeof(XTERM_BG( 64))-1, T(COLOR_BG_5F8700), 3, T("%X<#5F8700>"), 11},
{ CS_BG( 65), CS_BACKGROUND, XTERM_BG( 65), sizeof(XTERM_BG( 65))-1, T(COLOR_BG_5F875F), 3, T("%X<#5F875F>"), 11},
{ CS_BG( 66), CS_BACKGROUND, XTERM_BG( 66), sizeof(XTERM_BG( 66))-1, T(COLOR_BG_5F8787), 3, T("%X<#5F8787>"), 11},
{ CS_BG( 67), CS_BACKGROUND, XTERM_BG( 67), sizeof(XTERM_BG( 67))-1, T(COLOR_BG_5F87AF), 3, T("%X<#5F87AF>"), 11},
{ CS_BG( 68), CS_BACKGROUND, XTERM_BG( 68), sizeof(XTERM_BG( 68))-1, T(COLOR_BG_5F87D7), 3, T("%X<#5F87D7>"), 11},
{ CS_BG( 69), CS_BACKGROUND, XTERM_BG( 69), sizeof(XTERM_BG( 69))-1, T(COLOR_BG_5F87FF), 3, T("%X<#5F87FF>"), 11},
{ CS_BG( 70), CS_BACKGROUND, XTERM_BG( 70), sizeof(XTERM_BG( 70))-1, T(COLOR_BG_5FAF00), 3, T("%X<#5FAF00>"), 11},
{ CS_BG( 71), CS_BACKGROUND, XTERM_BG( 71), sizeof(XTERM_BG( 71))-1, T(COLOR_BG_5FAF5F), 3, T("%X<#5FAF5F>"), 11},
{ CS_BG( 72), CS_BACKGROUND, XTERM_BG( 72), sizeof(XTERM_BG( 72))-1, T(COLOR_BG_5FAF87), 3, T("%X<#5FAF87>"), 11},
{ CS_BG( 73), CS_BACKGROUND, XTERM_BG( 73), sizeof(XTERM_BG( 73))-1, T(COLOR_BG_5FAFAF), 3, T("%X<#5FAFAF>"), 11},
{ CS_BG( 74), CS_BACKGROUND, XTERM_BG( 74), sizeof(XTERM_BG( 74))-1, T(COLOR_BG_5FAFD7), 3, T("%X<#5FAFD7>"), 11},
{ CS_BG( 75), CS_BACKGROUND, XTERM_BG( 75), sizeof(XTERM_BG( 75))-1, T(COLOR_BG_5FAFFF), 3, T("%X<#5FAFFF>"), 11},
{ CS_BG( 76), CS_BACKGROUND, XTERM_BG( 76), sizeof(XTERM_BG( 76))-1, T(COLOR_BG_5FD700), 3, T("%X<#5FD700>"), 11},
{ CS_BG( 77), CS_BACKGROUND, XTERM_BG( 77), sizeof(XTERM_BG( 77))-1, T(COLOR_BG_5FD75F), 3, T("%X<#5FD75F>"), 11},
{ CS_BG( 78), CS_BACKGROUND, XTERM_BG( 78), sizeof(XTERM_BG( 78))-1, T(COLOR_BG_5FD787), 3, T("%X<#5FD787>"), 11},
{ CS_BG( 79), CS_BACKGROUND, XTERM_BG( 79), sizeof(XTERM_BG( 79))-1, T(COLOR_BG_5FD7AF), 3, T("%X<#5FD7AF>"), 11},
{ CS_BG( 80), CS_BACKGROUND, XTERM_BG( 80), sizeof(XTERM_BG( 80))-1, T(COLOR_BG_5FD7D7), 3, T("%X<#5FD7D7>"), 11},
{ CS_BG( 81), CS_BACKGROUND, XTERM_BG( 81), sizeof(XTERM_BG( 81))-1, T(COLOR_BG_5FD7FF), 3, T("%X<#5FD7FF>"), 11},
{ CS_BG( 82), CS_BACKGROUND, XTERM_BG( 82), sizeof(XTERM_BG( 82))-1, T(COLOR_BG_5FFF00), 3, T("%X<#5FFF00>"), 11},
{ CS_BG( 83), CS_BACKGROUND, XTERM_BG( 83), sizeof(XTERM_BG( 83))-1, T(COLOR_BG_5FFF5F), 3, T("%X<#5FFF5F>"), 11},
{ CS_BG( 84), CS_BACKGROUND, XTERM_BG( 84), sizeof(XTERM_BG( 84))-1, T(COLOR_BG_5FFF87), 3, T("%X<#5FFF87>"), 11},
{ CS_BG( 85), CS_BACKGROUND, XTERM_BG( 85), sizeof(XTERM_BG( 85))-1, T(COLOR_BG_5FFFAF), 3, T("%X<#5FFFAF>"), 11},
{ CS_BG( 86), CS_BACKGROUND, XTERM_BG( 86), sizeof(XTERM_BG( 86))-1, T(COLOR_BG_5FFFD7), 3, T("%X<#5FFFD7>"), 11},
{ CS_BG( 87), CS_BACKGROUND, XTERM_BG( 87), sizeof(XTERM_BG( 87))-1, T(COLOR_BG_5FFFFF), 3, T("%X<#5FFFFF>"), 11},
{ CS_BG( 88), CS_BACKGROUND, XTERM_BG( 88), sizeof(XTERM_BG( 88))-1, T(COLOR_BG_870000), 3, T("%X<#870000>"), 11},
{ CS_BG( 89), CS_BACKGROUND, XTERM_BG( 89), sizeof(XTERM_BG( 89))-1, T(COLOR_BG_87005F), 3, T("%X<#87005F>"), 11},
{ CS_BG( 90), CS_BACKGROUND, XTERM_BG( 90), sizeof(XTERM_BG( 90))-1, T(COLOR_BG_870087), 3, T("%X<#870087>"), 11},
{ CS_BG( 91), CS_BACKGROUND, XTERM_BG( 91), sizeof(XTERM_BG( 91))-1, T(COLOR_BG_8700AF), 3, T("%X<#8700AF>"), 11},
{ CS_BG( 92), CS_BACKGROUND, XTERM_BG( 92), sizeof(XTERM_BG( 92))-1, T(COLOR_BG_8700D7), 3, T("%X<#8700D7>"), 11},
{ CS_BG( 93), CS_BACKGROUND, XTERM_BG( 93), sizeof(XTERM_BG( 93))-1, T(COLOR_BG_8700FF), 3, T("%X<#8700FF>"), 11},
{ CS_BG( 94), CS_BACKGROUND, XTERM_BG( 94), sizeof(XTERM_BG( 94))-1, T(COLOR_BG_875F00), 3, T("%X<#875F00>"), 11},
{ CS_BG( 95), CS_BACKGROUND, XTERM_BG( 95), sizeof(XTERM_BG( 95))-1, T(COLOR_BG_875F5F), 3, T("%X<#875F5F>"), 11},
{ CS_BG( 96), CS_BACKGROUND, XTERM_BG( 96), sizeof(XTERM_BG( 96))-1, T(COLOR_BG_875F87), 3, T("%X<#875F87>"), 11},
{ CS_BG( 97), CS_BACKGROUND, XTERM_BG( 97), sizeof(XTERM_BG( 97))-1, T(COLOR_BG_875FAF), 3, T("%X<#875FAF>"), 11},
{ CS_BG( 98), CS_BACKGROUND, XTERM_BG( 98), sizeof(XTERM_BG( 98))-1, T(COLOR_BG_875FD7), 3, T("%X<#875FD7>"), 11},
{ CS_BG( 99), CS_BACKGROUND, XTERM_BG( 99), sizeof(XTERM_BG( 99))-1, T(COLOR_BG_875FFF), 3, T("%X<#875FFF>"), 11},
{ CS_BG(100), CS_BACKGROUND, XTERM_BG(100), sizeof(XTERM_BG(100))-1, T(COLOR_BG_878700), 3, T("%X<#878700>"), 11},
{ CS_BG(101), CS_BACKGROUND, XTERM_BG(101), sizeof(XTERM_BG(101))-1, T(COLOR_BG_87875F), 3, T("%X<#87875F>"), 11},
{ CS_BG(102), CS_BACKGROUND, XTERM_BG(102), sizeof(XTERM_BG(102))-1, T(COLOR_BG_878787), 3, T("%X<#878787>"), 11},
{ CS_BG(103), CS_BACKGROUND, XTERM_BG(103), sizeof(XTERM_BG(103))-1, T(COLOR_BG_8787AF), 3, T("%X<#8787AF>"), 11},
{ CS_BG(104), CS_BACKGROUND, XTERM_BG(104), sizeof(XTERM_BG(104))-1, T(COLOR_BG_8787D7), 3, T("%X<#8787D7>"), 11},
{ CS_BG(105), CS_BACKGROUND, XTERM_BG(105), sizeof(XTERM_BG(105))-1, T(COLOR_BG_8787FF), 3, T("%X<#8787FF>"), 11},
{ CS_BG(106), CS_BACKGROUND, XTERM_BG(106), sizeof(XTERM_BG(106))-1, T(COLOR_BG_87AF00), 3, T("%X<#87AF00>"), 11},
{ CS_BG(107), CS_BACKGROUND, XTERM_BG(107), sizeof(XTERM_BG(107))-1, T(COLOR_BG_87AF5F), 3, T("%X<#87AF5F>"), 11},
{ CS_BG(108), CS_BACKGROUND, XTERM_BG(108), sizeof(XTERM_BG(108))-1, T(COLOR_BG_87AF87), 3, T("%X<#87AF87>"), 11},
{ CS_BG(109), CS_BACKGROUND, XTERM_BG(109), sizeof(XTERM_BG(109))-1, T(COLOR_BG_87AFAF), 3, T("%X<#87AFAF>"), 11},
{ CS_BG(110), CS_BACKGROUND, XTERM_BG(110), sizeof(XTERM_BG(110))-1, T(COLOR_BG_87AFD7), 3, T("%X<#87AFD7>"), 11},
{ CS_BG(111), CS_BACKGROUND, XTERM_BG(111), sizeof(XTERM_BG(111))-1, T(COLOR_BG_87AFFF), 3, T("%X<#87AFFF>"), 11},
{ CS_BG(112), CS_BACKGROUND, XTERM_BG(112), sizeof(XTERM_BG(112))-1, T(COLOR_BG_87D700), 3, T("%X<#87D700>"), 11},
{ CS_BG(113), CS_BACKGROUND, XTERM_BG(113), sizeof(XTERM_BG(113))-1, T(COLOR_BG_87D75A), 3, T("%X<#87D75A>"), 11},
{ CS_BG(114), CS_BACKGROUND, XTERM_BG(114), sizeof(XTERM_BG(114))-1, T(COLOR_BG_87D787), 3, T("%X<#87D787>"), 11},
{ CS_BG(115), CS_BACKGROUND, XTERM_BG(115), sizeof(XTERM_BG(115))-1, T(COLOR_BG_87D7AF), 3, T("%X<#87D7AF>"), 11},
{ CS_BG(116), CS_BACKGROUND, XTERM_BG(116), sizeof(XTERM_BG(116))-1, T(COLOR_BG_87D7D7), 3, T("%X<#87D7D7>"), 11},
{ CS_BG(117), CS_BACKGROUND, XTERM_BG(117), sizeof(XTERM_BG(117))-1, T(COLOR_BG_87D7FF), 3, T("%X<#87D7FF>"), 11},
{ CS_BG(118), CS_BACKGROUND, XTERM_BG(118), sizeof(XTERM_BG(118))-1, T(COLOR_BG_87FF00), 3, T("%X<#87FF00>"), 11},
{ CS_BG(119), CS_BACKGROUND, XTERM_BG(119), sizeof(XTERM_BG(119))-1, T(COLOR_BG_87FF5F), 3, T("%X<#87FF5F>"), 11},
{ CS_BG(120), CS_BACKGROUND, XTERM_BG(120), sizeof(XTERM_BG(120))-1, T(COLOR_BG_87FF87), 3, T("%X<#87FF87>"), 11},
{ CS_BG(121), CS_BACKGROUND, XTERM_BG(121), sizeof(XTERM_BG(121))-1, T(COLOR_BG_87FFAF), 3, T("%X<#87FFAF>"), 11},
{ CS_BG(122), CS_BACKGROUND, XTERM_BG(122), sizeof(XTERM_BG(122))-1, T(COLOR_BG_87FFD7), 3, T("%X<#87FFD7>"), 11},
{ CS_BG(123), CS_BACKGROUND, XTERM_BG(123), sizeof(XTERM_BG(123))-1, T(COLOR_BG_87FFFF), 3, T("%X<#87FFFF>"), 11},
{ CS_BG(124), CS_BACKGROUND, XTERM_BG(124), sizeof(XTERM_BG(124))-1, T(COLOR_BG_AF0000), 3, T("%X<#AF0000>"), 11},
{ CS_BG(125), CS_BACKGROUND, XTERM_BG(125), sizeof(XTERM_BG(125))-1, T(COLOR_BG_AF005F), 3, T("%X<#AF005F>"), 11},
{ CS_BG(126), CS_BACKGROUND, XTERM_BG(126), sizeof(XTERM_BG(126))-1, T(COLOR_BG_AF0087), 3, T("%X<#AF0087>"), 11},
{ CS_BG(127), CS_BACKGROUND, XTERM_BG(127), sizeof(XTERM_BG(127))-1, T(COLOR_BG_AF00AF), 3, T("%X<#AF00AF>"), 11},
{ CS_BG(128), CS_BACKGROUND, XTERM_BG(128), sizeof(XTERM_BG(128))-1, T(COLOR_BG_AF00D7), 3, T("%X<#AF00D7>"), 11},
{ CS_BG(129), CS_BACKGROUND, XTERM_BG(129), sizeof(XTERM_BG(129))-1, T(COLOR_BG_AF00FF), 3, T("%X<#AF00FF>"), 11},
{ CS_BG(130), CS_BACKGROUND, XTERM_BG(130), sizeof(XTERM_BG(130))-1, T(COLOR_BG_AF5F00), 3, T("%X<#AF5F00>"), 11},
{ CS_BG(131), CS_BACKGROUND, XTERM_BG(131), sizeof(XTERM_BG(131))-1, T(COLOR_BG_AF5F5F), 3, T("%X<#AF5F5F>"), 11},
{ CS_BG(132), CS_BACKGROUND, XTERM_BG(132), sizeof(XTERM_BG(132))-1, T(COLOR_BG_AF5F87), 3, T("%X<#AF5F87>"), 11},
{ CS_BG(133), CS_BACKGROUND, XTERM_BG(133), sizeof(XTERM_BG(133))-1, T(COLOR_BG_AF5FAF), 3, T("%X<#AF5FAF>"), 11},
{ CS_BG(134), CS_BACKGROUND, XTERM_BG(134), sizeof(XTERM_BG(134))-1, T(COLOR_BG_AF5FD7), 3, T("%X<#AF5FD7>"), 11},
{ CS_BG(135), CS_BACKGROUND, XTERM_BG(135), sizeof(XTERM_BG(135))-1, T(COLOR_BG_AF5FFF), 3, T("%X<#AF5FFF>"), 11},
{ CS_BG(136), CS_BACKGROUND, XTERM_BG(136), sizeof(XTERM_BG(136))-1, T(COLOR_BG_AF8700), 3, T("%X<#AF8700>"), 11},
{ CS_BG(137), CS_BACKGROUND, XTERM_BG(137), sizeof(XTERM_BG(137))-1, T(COLOR_BG_AF875F), 3, T("%X<#AF875F>"), 11},
{ CS_BG(138), CS_BACKGROUND, XTERM_BG(138), sizeof(XTERM_BG(138))-1, T(COLOR_BG_AF8787), 3, T("%X<#AF8787>"), 11},
{ CS_BG(139), CS_BACKGROUND, XTERM_BG(139), sizeof(XTERM_BG(139))-1, T(COLOR_BG_AF87AF), 3, T("%X<#AF87AF>"), 11},
{ CS_BG(140), CS_BACKGROUND, XTERM_BG(140), sizeof(XTERM_BG(140))-1, T(COLOR_BG_AF87D7), 3, T("%X<#AF87D7>"), 11},
{ CS_BG(141), CS_BACKGROUND, XTERM_BG(141), sizeof(XTERM_BG(141))-1, T(COLOR_BG_AF87FF), 3, T("%X<#AF87FF>"), 11},
{ CS_BG(142), CS_BACKGROUND, XTERM_BG(142), sizeof(XTERM_BG(142))-1, T(COLOR_BG_AFAF00), 3, T("%X<#AFAF00>"), 11},
{ CS_BG(143), CS_BACKGROUND, XTERM_BG(143), sizeof(XTERM_BG(143))-1, T(COLOR_BG_AFAF5F), 3, T("%X<#AFAF5F>"), 11},
{ CS_BG(144), CS_BACKGROUND, XTERM_BG(144), sizeof(XTERM_BG(144))-1, T(COLOR_BG_AFAF87), 3, T("%X<#AFAF87>"), 11},
{ CS_BG(145), CS_BACKGROUND, XTERM_BG(145), sizeof(XTERM_BG(145))-1, T(COLOR_BG_AFAFAF), 3, T("%X<#AFAFAF>"), 11},
{ CS_BG(146), CS_BACKGROUND, XTERM_BG(146), sizeof(XTERM_BG(146))-1, T(COLOR_BG_AFAFD7), 3, T("%X<#AFAFD7>"), 11},
{ CS_BG(147), CS_BACKGROUND, XTERM_BG(147), sizeof(XTERM_BG(147))-1, T(COLOR_BG_AFAFFF), 3, T("%X<#AFAFFF>"), 11},
{ CS_BG(148), CS_BACKGROUND, XTERM_BG(148), sizeof(XTERM_BG(148))-1, T(COLOR_BG_AFD700), 3, T("%X<#AFD700>"), 11},
{ CS_BG(149), CS_BACKGROUND, XTERM_BG(149), sizeof(XTERM_BG(149))-1, T(COLOR_BG_AFD75F), 3, T("%X<#AFD75F>"), 11},
{ CS_BG(150), CS_BACKGROUND, XTERM_BG(150), sizeof(XTERM_BG(150))-1, T(COLOR_BG_AFD787), 3, T("%X<#AFD787>"), 11},
{ CS_BG(151), CS_BACKGROUND, XTERM_BG(151), sizeof(XTERM_BG(151))-1, T(COLOR_BG_AFD7AF), 3, T("%X<#AFD7AF>"), 11},
{ CS_BG(152), CS_BACKGROUND, XTERM_BG(152), sizeof(XTERM_BG(152))-1, T(COLOR_BG_AFD7D7), 3, T("%X<#AFD7D7>"), 11},
{ CS_BG(153), CS_BACKGROUND, XTERM_BG(153), sizeof(XTERM_BG(153))-1, T(COLOR_BG_AFD7FF), 3, T("%X<#AFD7FF>"), 11},
{ CS_BG(154), CS_BACKGROUND, XTERM_BG(154), sizeof(XTERM_BG(154))-1, T(COLOR_BG_AFFF00), 3, T("%X<#AFFF00>"), 11},
{ CS_BG(155), CS_BACKGROUND, XTERM_BG(155), sizeof(XTERM_BG(155))-1, T(COLOR_BG_AFFF5F), 3, T("%X<#AFFF5F>"), 11},
{ CS_BG(156), CS_BACKGROUND, XTERM_BG(156), sizeof(XTERM_BG(156))-1, T(COLOR_BG_AFFF87), 3, T("%X<#AFFF87>"), 11},
{ CS_BG(157), CS_BACKGROUND, XTERM_BG(157), sizeof(XTERM_BG(157))-1, T(COLOR_BG_AFFFAF), 3, T("%X<#AFFFAF>"), 11},
{ CS_BG(158), CS_BACKGROUND, XTERM_BG(158), sizeof(XTERM_BG(158))-1, T(COLOR_BG_AFFFD7), 3, T("%X<#AFFFD7>"), 11},
{ CS_BG(159), CS_BACKGROUND, XTERM_BG(159), sizeof(XTERM_BG(159))-1, T(COLOR_BG_AFFFFF), 3, T("%X<#AFFFFF>"), 11},
{ CS_BG(160), CS_BACKGROUND, XTERM_BG(160), sizeof(XTERM_BG(160))-1, T(COLOR_BG_D70000), 3, T("%X<#D70000>"), 11},
{ CS_BG(161), CS_BACKGROUND, XTERM_BG(161), sizeof(XTERM_BG(161))-1, T(COLOR_BG_D7005F), 3, T("%X<#D7005F>"), 11},
{ CS_BG(162), CS_BACKGROUND, XTERM_BG(162), sizeof(XTERM_BG(162))-1, T(COLOR_BG_D70087), 3, T("%X<#D70087>"), 11},
{ CS_BG(163), CS_BACKGROUND, XTERM_BG(163), sizeof(XTERM_BG(163))-1, T(COLOR_BG_D700AF), 3, T("%X<#D700AF>"), 11},
{ CS_BG(164), CS_BACKGROUND, XTERM_BG(164), sizeof(XTERM_BG(164))-1, T(COLOR_BG_D700D7), 3, T("%X<#D700D7>"), 11},
{ CS_BG(165), CS_BACKGROUND, XTERM_BG(165), sizeof(XTERM_BG(165))-1, T(COLOR_BG_D700FF), 3, T("%X<#D700FF>"), 11},
{ CS_BG(166), CS_BACKGROUND, XTERM_BG(166), sizeof(XTERM_BG(166))-1, T(COLOR_BG_D75F00), 3, T("%X<#D75F00>"), 11},
{ CS_BG(167), CS_BACKGROUND, XTERM_BG(167), sizeof(XTERM_BG(167))-1, T(COLOR_BG_D75F5F), 3, T("%X<#D75F5F>"), 11},
{ CS_BG(168), CS_BACKGROUND, XTERM_BG(168), sizeof(XTERM_BG(168))-1, T(COLOR_BG_D75F87), 3, T("%X<#D75F87>"), 11},
{ CS_BG(169), CS_BACKGROUND, XTERM_BG(169), sizeof(XTERM_BG(169))-1, T(COLOR_BG_D75FAF), 3, T("%X<#D75FAF>"), 11},
{ CS_BG(170), CS_BACKGROUND, XTERM_BG(170), sizeof(XTERM_BG(170))-1, T(COLOR_BG_D75FD7), 3, T("%X<#D75FD7>"), 11},
{ CS_BG(171), CS_BACKGROUND, XTERM_BG(171), sizeof(XTERM_BG(171))-1, T(COLOR_BG_D75FFF), 3, T("%X<#D75FFF>"), 11},
{ CS_BG(172), CS_BACKGROUND, XTERM_BG(172), sizeof(XTERM_BG(172))-1, T(COLOR_BG_D78700), 3, T("%X<#D78700>"), 11},
{ CS_BG(173), CS_BACKGROUND, XTERM_BG(173), sizeof(XTERM_BG(173))-1, T(COLOR_BG_D7875A), 3, T("%X<#D7875A>"), 11},
{ CS_BG(174), CS_BACKGROUND, XTERM_BG(174), sizeof(XTERM_BG(174))-1, T(COLOR_BG_D78787), 3, T("%X<#D78787>"), 11},
{ CS_BG(175), CS_BACKGROUND, XTERM_BG(175), sizeof(XTERM_BG(175))-1, T(COLOR_BG_D787AF), 3, T("%X<#D787AF>"), 11},
{ CS_BG(176), CS_BACKGROUND, XTERM_BG(176), sizeof(XTERM_BG(176))-1, T(COLOR_BG_D787D7), 3, T("%X<#D787D7>"), 11},
{ CS_BG(177), CS_BACKGROUND, XTERM_BG(177), sizeof(XTERM_BG(177))-1, T(COLOR_BG_D787FF), 3, T("%X<#D787FF>"), 11},
{ CS_BG(178), CS_BACKGROUND, XTERM_BG(178), sizeof(XTERM_BG(178))-1, T(COLOR_BG_D7AF00), 3, T("%X<#D7AF00>"), 11},
{ CS_BG(179), CS_BACKGROUND, XTERM_BG(179), sizeof(XTERM_BG(179))-1, T(COLOR_BG_D7AF5A), 3, T("%X<#D7AF5A>"), 11},
{ CS_BG(180), CS_BACKGROUND, XTERM_BG(180), sizeof(XTERM_BG(180))-1, T(COLOR_BG_D7AF87), 3, T("%X<#D7AF87>"), 11},
{ CS_BG(181), CS_BACKGROUND, XTERM_BG(181), sizeof(XTERM_BG(181))-1, T(COLOR_BG_D7AFAF), 3, T("%X<#D7AFAF>"), 11},
{ CS_BG(182), CS_BACKGROUND, XTERM_BG(182), sizeof(XTERM_BG(182))-1, T(COLOR_BG_D7AFD7), 3, T("%X<#D7AFD7>"), 11},
{ CS_BG(183), CS_BACKGROUND, XTERM_BG(183), sizeof(XTERM_BG(183))-1, T(COLOR_BG_D7AFFF), 3, T("%X<#D7AFFF>"), 11},
{ CS_BG(184), CS_BACKGROUND, XTERM_BG(184), sizeof(XTERM_BG(184))-1, T(COLOR_BG_D7D700), 3, T("%X<#D7D700>"), 11},
{ CS_BG(185), CS_BACKGROUND, XTERM_BG(185), sizeof(XTERM_BG(185))-1, T(COLOR_BG_D7D75F), 3, T("%X<#D7D75F>"), 11},
{ CS_BG(186), CS_BACKGROUND, XTERM_BG(186), sizeof(XTERM_BG(186))-1, T(COLOR_BG_D7D787), 3, T("%X<#D7D787>"), 11},
{ CS_BG(187), CS_BACKGROUND, XTERM_BG(187), sizeof(XTERM_BG(187))-1, T(COLOR_BG_D7D7AF), 3, T("%X<#D7D7AF>"), 11},
{ CS_BG(188), CS_BACKGROUND, XTERM_BG(188), sizeof(XTERM_BG(188))-1, T(COLOR_BG_D7D7D7), 3, T("%X<#D7D7D7>"), 11},
{ CS_BG(189), CS_BACKGROUND, XTERM_BG(189), sizeof(XTERM_BG(189))-1, T(COLOR_BG_D7D7FF), 3, T("%X<#D7D7FF>"), 11},
{ CS_BG(190), CS_BACKGROUND, XTERM_BG(190), sizeof(XTERM_BG(190))-1, T(COLOR_BG_D7FF00), 3, T("%X<#D7FF00>"), 11},
{ CS_BG(191), CS_BACKGROUND, XTERM_BG(191), sizeof(XTERM_BG(191))-1, T(COLOR_BG_D7FF5F), 3, T("%X<#D7FF5F>"), 11},
{ CS_BG(192), CS_BACKGROUND, XTERM_BG(192), sizeof(XTERM_BG(192))-1, T(COLOR_BG_D7FF87), 3, T("%X<#D7FF87>"), 11},
{ CS_BG(193), CS_BACKGROUND, XTERM_BG(193), sizeof(XTERM_BG(193))-1, T(COLOR_BG_D7FFAF), 3, T("%X<#D7FFAF>"), 11},
{ CS_BG(194), CS_BACKGROUND, XTERM_BG(194), sizeof(XTERM_BG(194))-1, T(COLOR_BG_D7FFD7), 3, T("%X<#D7FFD7>"), 11},
{ CS_BG(195), CS_BACKGROUND, XTERM_BG(195), sizeof(XTERM_BG(195))-1, T(COLOR_BG_D7FFFF), 3, T("%X<#D7FFFF>"), 11},
{ CS_BG(196), CS_BACKGROUND, XTERM_BG(196), sizeof(XTERM_BG(196))-1, T(COLOR_BG_FF0000), 3, T("%X<#FF0000>"), 11},
{ CS_BG(197), CS_BACKGROUND, XTERM_BG(197), sizeof(XTERM_BG(197))-1, T(COLOR_BG_FF005F), 3, T("%X<#FF005F>"), 11},
{ CS_BG(198), CS_BACKGROUND, XTERM_BG(198), sizeof(XTERM_BG(198))-1, T(COLOR_BG_FF0087), 3, T("%X<#FF0087>"), 11},
{ CS_BG(199), CS_BACKGROUND, XTERM_BG(199), sizeof(XTERM_BG(199))-1, T(COLOR_BG_FF00AF), 3, T("%X<#FF00AF>"), 11},
{ CS_BG(200), CS_BACKGROUND, XTERM_BG(200), sizeof(XTERM_BG(200))-1, T(COLOR_BG_FF00D7), 3, T("%X<#FF00D7>"), 11},
{ CS_BG(201), CS_BACKGROUND, XTERM_BG(201), sizeof(XTERM_BG(201))-1, T(COLOR_BG_FF00FF), 3, T("%X<#FF00FF>"), 11},
{ CS_BG(202), CS_BACKGROUND, XTERM_BG(202), sizeof(XTERM_BG(202))-1, T(COLOR_BG_FF5F00), 3, T("%X<#FF5F00>"), 11},
{ CS_BG(203), CS_BACKGROUND, XTERM_BG(203), sizeof(XTERM_BG(203))-1, T(COLOR_BG_FF5F5F), 3, T("%X<#FF5F5F>"), 11},
{ CS_BG(204), CS_BACKGROUND, XTERM_BG(204), sizeof(XTERM_BG(204))-1, T(COLOR_BG_FF5F87), 3, T("%X<#FF5F87>"), 11},
{ CS_BG(205), CS_BACKGROUND, XTERM_BG(205), sizeof(XTERM_BG(205))-1, T(COLOR_BG_FF5FAF), 3, T("%X<#FF5FAF>"), 11},
{ CS_BG(206), CS_BACKGROUND, XTERM_BG(206), sizeof(XTERM_BG(206))-1, T(COLOR_BG_FF5FD7), 3, T("%X<#FF5FD7>"), 11},
{ CS_BG(207), CS_BACKGROUND, XTERM_BG(207), sizeof(XTERM_BG(207))-1, T(COLOR_BG_FF5FFF), 3, T("%X<#FF5FFF>"), 11},
{ CS_BG(208), CS_BACKGROUND, XTERM_BG(208), sizeof(XTERM_BG(208))-1, T(COLOR_BG_FF8700), 3, T("%X<#FF8700>"), 11},
{ CS_BG(209), CS_BACKGROUND, XTERM_BG(209), sizeof(XTERM_BG(209))-1, T(COLOR_BG_FF875F), 3, T("%X<#FF875F>"), 11},
{ CS_BG(210), CS_BACKGROUND, XTERM_BG(210), sizeof(XTERM_BG(210))-1, T(COLOR_BG_FF8787), 3, T("%X<#FF8787>"), 11},
{ CS_BG(211), CS_BACKGROUND, XTERM_BG(211), sizeof(XTERM_BG(211))-1, T(COLOR_BG_FF87AF), 3, T("%X<#FF87AF>"), 11},
{ CS_BG(212), CS_BACKGROUND, XTERM_BG(212), sizeof(XTERM_BG(212))-1, T(COLOR_BG_FF87D7), 3, T("%X<#FF87D7>"), 11},
{ CS_BG(213), CS_BACKGROUND, XTERM_BG(213), sizeof(XTERM_BG(213))-1, T(COLOR_BG_FF87FF), 3, T("%X<#FF87FF>"), 11},
{ CS_BG(214), CS_BACKGROUND, XTERM_BG(214), sizeof(XTERM_BG(214))-1, T(COLOR_BG_FFAF00), 3, T("%X<#FFAF00>"), 11},
{ CS_BG(215), CS_BACKGROUND, XTERM_BG(215), sizeof(XTERM_BG(215))-1, T(COLOR_BG_FFAF5F), 3, T("%X<#FFAF5F>"), 11},
{ CS_BG(216), CS_BACKGROUND, XTERM_BG(216), sizeof(XTERM_BG(216))-1, T(COLOR_BG_FFAF87), 3, T("%X<#FFAF87>"), 11},
{ CS_BG(217), CS_BACKGROUND, XTERM_BG(217), sizeof(XTERM_BG(217))-1, T(COLOR_BG_FFAFAF), 3, T("%X<#FFAFAF>"), 11},
{ CS_BG(218), CS_BACKGROUND, XTERM_BG(218), sizeof(XTERM_BG(218))-1, T(COLOR_BG_FFAFD7), 3, T("%X<#FFAFD7>"), 11},
{ CS_BG(219), CS_BACKGROUND, XTERM_BG(219), sizeof(XTERM_BG(219))-1, T(COLOR_BG_FFAFFF), 3, T("%X<#FFAFFF>"), 11},
{ CS_BG(220), CS_BACKGROUND, XTERM_BG(220), sizeof(XTERM_BG(220))-1, T(COLOR_BG_FFD700), 3, T("%X<#FFD700>"), 11},
{ CS_BG(221), CS_BACKGROUND, XTERM_BG(221), sizeof(XTERM_BG(221))-1, T(COLOR_BG_FFD75F), 3, T("%X<#FFD75F>"), 11},
{ CS_BG(222), CS_BACKGROUND, XTERM_BG(222), sizeof(XTERM_BG(222))-1, T(COLOR_BG_FFD787), 3, T("%X<#FFD787>"), 11},
{ CS_BG(223), CS_BACKGROUND, XTERM_BG(223), sizeof(XTERM_BG(223))-1, T(COLOR_BG_FFD7AF), 3, T("%X<#FFD7AF>"), 11},
{ CS_BG(224), CS_BACKGROUND, XTERM_BG(224), sizeof(XTERM_BG(224))-1, T(COLOR_BG_FFD7D7), 3, T("%X<#FFD7D7>"), 11},
{ CS_BG(225), CS_BACKGROUND, XTERM_BG(225), sizeof(XTERM_BG(225))-1, T(COLOR_BG_FFD7FF), 3, T("%X<#FFD7FF>"), 11},
{ CS_BG(226), CS_BACKGROUND, XTERM_BG(226), sizeof(XTERM_BG(226))-1, T(COLOR_BG_FFFF00), 3, T("%X<#FFFF00>"), 11},
{ CS_BG(227), CS_BACKGROUND, XTERM_BG(227), sizeof(XTERM_BG(227))-1, T(COLOR_BG_FFFF5F), 3, T("%X<#FFFF5F>"), 11},
{ CS_BG(228), CS_BACKGROUND, XTERM_BG(228), sizeof(XTERM_BG(228))-1, T(COLOR_BG_FFFF87), 3, T("%X<#FFFF87>"), 11},
{ CS_BG(229), CS_BACKGROUND, XTERM_BG(229), sizeof(XTERM_BG(229))-1, T(COLOR_BG_FFFFAF), 3, T("%X<#FFFFAF>"), 11},
{ CS_BG(230), CS_BACKGROUND, XTERM_BG(230), sizeof(XTERM_BG(230))-1, T(COLOR_BG_FFFFD7), 3, T("%X<#FFFFD7>"), 11},
2010-08-11 22:07:01 -07:00
{ CS_BG(231), CS_BACKGROUND, XTERM_BG(231), sizeof(XTERM_BG(231))-1, T(COLOR_BG_FFFFFF_2),3, T("%X<#FFFFFF>"), 11},
{ CS_BG(232), CS_BACKGROUND, XTERM_BG(232), sizeof(XTERM_BG(232))-1, T(COLOR_BG_080808), 3, T("%X<#080808>"), 11},
{ CS_BG(233), CS_BACKGROUND, XTERM_BG(233), sizeof(XTERM_BG(233))-1, T(COLOR_BG_121212), 3, T("%X<#121212>"), 11},
{ CS_BG(234), CS_BACKGROUND, XTERM_BG(234), sizeof(XTERM_BG(234))-1, T(COLOR_BG_1C1C1C), 3, T("%X<#1C1C1C>"), 11},
{ CS_BG(235), CS_BACKGROUND, XTERM_BG(235), sizeof(XTERM_BG(235))-1, T(COLOR_BG_262626), 3, T("%X<#262626>"), 11},
{ CS_BG(236), CS_BACKGROUND, XTERM_BG(236), sizeof(XTERM_BG(236))-1, T(COLOR_BG_303030), 3, T("%X<#303030>"), 11},
{ CS_BG(237), CS_BACKGROUND, XTERM_BG(237), sizeof(XTERM_BG(237))-1, T(COLOR_BG_3A3A3A), 3, T("%X<#3A3A3A>"), 11},
{ CS_BG(238), CS_BACKGROUND, XTERM_BG(238), sizeof(XTERM_BG(238))-1, T(COLOR_BG_444444), 3, T("%X<#444444>"), 11},
{ CS_BG(239), CS_BACKGROUND, XTERM_BG(239), sizeof(XTERM_BG(239))-1, T(COLOR_BG_4E4E4E), 3, T("%X<#4E4E4E>"), 11},
{ CS_BG(240), CS_BACKGROUND, XTERM_BG(240), sizeof(XTERM_BG(240))-1, T(COLOR_BG_585858), 3, T("%X<#585858>"), 11},
{ CS_BG(241), CS_BACKGROUND, XTERM_BG(241), sizeof(XTERM_BG(241))-1, T(COLOR_BG_626262), 3, T("%X<#626262>"), 11},
{ CS_BG(242), CS_BACKGROUND, XTERM_BG(242), sizeof(XTERM_BG(242))-1, T(COLOR_BG_6C6C6C), 3, T("%X<#6C6C6C>"), 11},
{ CS_BG(243), CS_BACKGROUND, XTERM_BG(243), sizeof(XTERM_BG(243))-1, T(COLOR_BG_767676), 3, T("%X<#767676>"), 11},
{ CS_BG(244), CS_BACKGROUND, XTERM_BG(244), sizeof(XTERM_BG(244))-1, T(COLOR_BG_808080), 3, T("%X<#808080>"), 11},
{ CS_BG(245), CS_BACKGROUND, XTERM_BG(245), sizeof(XTERM_BG(245))-1, T(COLOR_BG_8A8A8A), 3, T("%X<#8A8A8A>"), 11},
{ CS_BG(246), CS_BACKGROUND, XTERM_BG(246), sizeof(XTERM_BG(246))-1, T(COLOR_BG_949494), 3, T("%X<#949494>"), 11},
{ CS_BG(247), CS_BACKGROUND, XTERM_BG(247), sizeof(XTERM_BG(247))-1, T(COLOR_BG_9E9E9E), 3, T("%X<#9E9E9E>"), 11},
{ CS_BG(248), CS_BACKGROUND, XTERM_BG(248), sizeof(XTERM_BG(248))-1, T(COLOR_BG_A8A8A8), 3, T("%X<#A8A8A8>"), 11},
{ CS_BG(249), CS_BACKGROUND, XTERM_BG(249), sizeof(XTERM_BG(249))-1, T(COLOR_BG_B2B2B2), 3, T("%X<#B2B2B2>"), 11},
{ CS_BG(250), CS_BACKGROUND, XTERM_BG(250), sizeof(XTERM_BG(250))-1, T(COLOR_BG_BCBCBC), 3, T("%X<#BCBCBC>"), 11},
{ CS_BG(251), CS_BACKGROUND, XTERM_BG(251), sizeof(XTERM_BG(251))-1, T(COLOR_BG_C6C6C6), 3, T("%X<#C6C6C6>"), 11},
{ CS_BG(252), CS_BACKGROUND, XTERM_BG(252), sizeof(XTERM_BG(252))-1, T(COLOR_BG_D0D0D0), 3, T("%X<#D0D0D0>"), 11},
{ CS_BG(253), CS_BACKGROUND, XTERM_BG(253), sizeof(XTERM_BG(253))-1, T(COLOR_BG_DADADA), 3, T("%X<#DADADA>"), 11},
{ CS_BG(254), CS_BACKGROUND, XTERM_BG(254), sizeof(XTERM_BG(254))-1, T(COLOR_BG_E4E4E4), 3, T("%X<#E4E4E4>"), 11},
{ CS_BG(255), CS_BACKGROUND, XTERM_BG(255), sizeof(XTERM_BG(255))-1, T(COLOR_BG_EEEEEE), 3, T("%X<#EEEEEE>"), 11},
};
/*! \brief Validate ColorState.
*
* Checks (with assertions) that the given ColorState is valid. This is
* useful during development and debugging, but if any of the assertions are
* false, the process ends.
*
* \param cs ColorState.
* \return None.
*/
inline void ValidateColorState(ColorState cs)
{
const ColorState Mask = static_cast<ColorState>(~(CS_FOREGROUND|CS_BACKGROUND|CS_ATTRS));
mux_assert((Mask & cs) == 0);
mux_assert((CS_FOREGROUND & cs) <= CS_FG_DEFAULT);
mux_assert((CS_BACKGROUND & cs) <= CS_BG_DEFAULT);
}
inline ColorState UpdateColorState(ColorState cs, int iColorCode, const UTF8 *pRaw = nullptr)
{
mux_assert(0 <= iColorCode && iColorCode <= COLOR_INDEX_LAST);
if (COLOR_INDEX_FG_24_CP1 <= iColorCode)
2010-08-11 15:50:24 -07:00
{
// 24-bit color: 2-code-point encoding.
// CP1 carries R high nibble + G, CP2 carries R low nibble + B.
// pRaw points to the 4-byte UTF-8 sequence: F3 Bx xx xx.
2010-08-11 15:50:24 -07:00
//
mux_assert(pRaw != nullptr);
unsigned int payload = mux_color_smp_payload(pRaw);
unsigned int hi = payload >> 8; // 4-bit nibble (CP1) or nibble (CP2)
unsigned int lo = payload & 0xFF; // G (CP1) or B (CP2)
switch (iColorCode)
2010-08-11 15:50:24 -07:00
{
case COLOR_INDEX_FG_24_CP1:
2010-08-11 15:50:24 -07:00
if (CS_FG_INDEXED & cs)
{
2022-03-12 14:09:34 -07:00
cs = (cs & ~CS_FOREGROUND) | rgb2cs(&palette[CS_FG_FIELD_INDEXED(cs)].rgb);
2010-08-11 15:50:24 -07:00
}
cs = (cs & ~CS_FOREGROUND_RED) | (static_cast<ColorState>(hi << 4) << 16);
cs = (cs & ~CS_FOREGROUND_GREEN) | (static_cast<ColorState>(lo) << 8);
break;
2010-08-11 15:50:24 -07:00
case COLOR_INDEX_FG_24_CP2:
if (CS_FG_INDEXED & cs)
2010-08-11 15:50:24 -07:00
{
cs = (cs & ~CS_FOREGROUND) | rgb2cs(&palette[CS_FG_FIELD_INDEXED(cs)].rgb);
2010-08-11 15:50:24 -07:00
}
{
ColorState red = (cs >> 16) & 0xF0;
cs = (cs & ~CS_FOREGROUND_RED) | ((red | hi) << 16);
2010-08-11 15:50:24 -07:00
}
cs = (cs & ~CS_FOREGROUND_BLUE) | static_cast<ColorState>(lo);
break;
case COLOR_INDEX_BG_24_CP1:
2010-08-11 15:50:24 -07:00
if (CS_BG_INDEXED & cs)
{
2022-03-12 14:09:34 -07:00
cs = (cs & ~CS_BACKGROUND) | (rgb2cs(&palette[CS_BG_FIELD_INDEXED(cs)].rgb) << 32);
2010-08-11 15:50:24 -07:00
}
cs = (cs & ~CS_BACKGROUND_RED) | (static_cast<ColorState>(hi << 4) << 48);
cs = (cs & ~CS_BACKGROUND_GREEN) | (static_cast<ColorState>(lo) << 40);
break;
2010-08-11 15:50:24 -07:00
case COLOR_INDEX_BG_24_CP2:
if (CS_BG_INDEXED & cs)
2010-08-11 15:50:24 -07:00
{
cs = (cs & ~CS_BACKGROUND) | (rgb2cs(&palette[CS_BG_FIELD_INDEXED(cs)].rgb) << 32);
2010-08-11 15:50:24 -07:00
}
{
ColorState red = (cs >> 48) & 0xF0;
cs = (cs & ~CS_BACKGROUND_RED) | ((red | hi) << 48);
2010-08-11 15:50:24 -07:00
}
cs = (cs & ~CS_BACKGROUND_BLUE) | (static_cast<ColorState>(lo) << 32);
break;
2010-08-11 15:50:24 -07:00
}
mux_assert((cs & ~CS_ALLBITS) == 0);
2010-08-11 15:50:24 -07:00
return cs;
}
mux_assert(iColorCode < sizeof(aColors)/sizeof(aColors[0]));
return (cs & ~aColors[iColorCode].csMask) | aColors[iColorCode].cs;
}
// Maximum binary transition length is:
//
// COLOR_RESET "\xEF\x94\x80"
// + COLOR_INTENSE "\xEF\x94\x81"
// + COLOR_UNDERLINE "\xEF\x94\x84"
// + COLOR_BLINK "\xEF\x94\x85"
// + COLOR_INVERSE "\xEF\x94\x87"
// + COLOR_FG_RED "\xEF\x98\x81"
// + COLOR_BG_WHITE "\xEF\x9C\x87"
//
// Each of the seven codes is 3 bytes or 21 bytes total. Plus two 24-bit
// SMP code points per layer (4 bytes each), for FG and BG = 4 code points.
//
#define COLOR_MAXIMUM_BINARY_TRANSITION_LENGTH (21+4*4)
// Emit a 4-byte SMP PUA color code point.
// block: 0=FG CP1, 1=FG CP2, 2=BG CP1, 3=BG CP2
// payload: 12-bit value ((nibble << 8) | channel_byte)
//
static inline void EmitSMPColor(UTF8 *buf, unsigned int block, unsigned int payload)
{
buf[0] = 0xF3;
buf[1] = static_cast<UTF8>(0xB0 + block);
buf[2] = static_cast<UTF8>(0x80 | ((payload >> 6) & 0x3F));
buf[3] = static_cast<UTF8>(0x80 | (payload & 0x3F));
}
// Generate the minimal color sequence that will transition from one color state
// to another.
//
static UTF8 *ColorTransitionBinary
(
ColorState csCurrent,
ColorState csNext,
size_t *nTransition
)
{
ValidateColorState(csCurrent);
ValidateColorState(csNext);
static UTF8 Buffer[COLOR_MAXIMUM_BINARY_TRANSITION_LENGTH+1];
if (csCurrent == csNext)
{
*nTransition = 0;
Buffer[0] = '\0';
return Buffer;
}
size_t i = 0;
// Do we need to go through the normal state?
//
if ( ((csCurrent & ~csNext) & CS_ATTRS)
|| ( (csNext & CS_BACKGROUND) == CS_BG_DEFAULT
&& (csCurrent & CS_BACKGROUND) != CS_BG_DEFAULT)
|| ( (csNext & CS_FOREGROUND) == CS_FG_DEFAULT
&& (csCurrent & CS_FOREGROUND) != CS_FG_DEFAULT))
{
memcpy(Buffer + i, COLOR_RESET, sizeof(COLOR_RESET)-1);
i += sizeof(COLOR_RESET)-1;
csCurrent = CS_NORMAL;
}
ColorState tmp = csCurrent ^ csNext;
if (CS_ATTRS & tmp)
{
for (unsigned int iAttr = COLOR_INDEX_ATTR; iAttr < COLOR_INDEX_FG; iAttr++)
{
if (aColors[iAttr].cs == (aColors[iAttr].csMask & tmp))
{
memcpy(Buffer + i, aColors[iAttr].pUTF, aColors[iAttr].nUTF);
i += aColors[iAttr].nUTF;
}
}
}
RGB rgb;
unsigned int iColor;
if (CS_FOREGROUND & tmp)
{
2010-08-11 15:50:24 -07:00
bool fExact;
if (CS_FG_INDEXED & csNext)
{
2010-08-11 18:15:00 -07:00
iColor = static_cast<unsigned int>(CS_FG_FIELD(csNext));
2010-08-11 15:50:24 -07:00
fExact = true;
}
else
{
cs2rgb(CS_FG_FIELD(csNext), &rgb);
2010-08-11 15:50:24 -07:00
iColor = FindNearestPaletteEntry(rgb, true);
fExact = ( palette[iColor].rgb.r == rgb.r
&& palette[iColor].rgb.g == rgb.g
&& palette[iColor].rgb.b == rgb.b);
}
2010-08-11 15:50:24 -07:00
if (iColor < COLOR_INDEX_DEFAULT)
{
2010-08-11 15:50:24 -07:00
memcpy(Buffer + i, aColors[COLOR_INDEX_FG + iColor].pUTF, aColors[COLOR_INDEX_FG + iColor].nUTF);
i += aColors[COLOR_INDEX_FG + iColor].nUTF;
if (!fExact)
{
// 2-code-point FG encoding: CP1 (block 0) + CP2 (block 1)
//
EmitSMPColor(Buffer + i, 0, ((rgb.r >> 4) << 8) | rgb.g);
i += 4;
EmitSMPColor(Buffer + i, 1, ((rgb.r & 0xF) << 8) | rgb.b);
i += 4;
2010-08-11 15:50:24 -07:00
}
}
}
if (CS_BACKGROUND & tmp)
{
2010-08-11 15:50:24 -07:00
bool fExact;
if (CS_BG_INDEXED & csNext)
{
2010-08-11 18:15:00 -07:00
iColor = static_cast<unsigned int>(CS_BG_FIELD(csNext));
2010-08-11 15:50:24 -07:00
fExact = true;
}
else
{
cs2rgb(CS_BG_FIELD(csNext), &rgb);
2010-08-11 15:50:24 -07:00
iColor = FindNearestPaletteEntry(rgb, true);
fExact = ( palette[iColor].rgb.r == rgb.r
&& palette[iColor].rgb.g == rgb.g
&& palette[iColor].rgb.b == rgb.b);
}
2010-08-11 15:50:24 -07:00
if (iColor < COLOR_INDEX_DEFAULT)
{
2010-08-11 15:50:24 -07:00
memcpy(Buffer + i, aColors[COLOR_INDEX_BG + iColor].pUTF, aColors[COLOR_INDEX_BG + iColor].nUTF);
i += aColors[COLOR_INDEX_BG + iColor].nUTF;
if (!fExact)
{
// 2-code-point BG encoding: CP1 (block 2) + CP2 (block 3)
//
EmitSMPColor(Buffer + i, 2, ((rgb.r >> 4) << 8) | rgb.g);
i += 4;
EmitSMPColor(Buffer + i, 3, ((rgb.r & 0xF) << 8) | rgb.b);
i += 4;
2010-08-11 15:50:24 -07:00
}
}
}
Buffer[i] = '\0';
*nTransition = i;
return Buffer;
}
// Maximum binary transition to normal is:
//
// COLOR_RESET "\xEF\x94\x80"
//
// The code is 3 bytes.
//
#define COLOR_MAXIMUM_BINARY_NORMAL 3
// Generate the minimal color sequence that will transition from one color state
// to the normal state.
//
static const UTF8 *ColorBinaryNormal
(
ColorState csCurrent,
size_t *nTransition
)
{
ValidateColorState(csCurrent);
if (csCurrent == CS_NORMAL)
{
*nTransition = 0;
return T("");
}
else
{
*nTransition = sizeof(COLOR_RESET) - 1;
return T(COLOR_RESET);
}
}
// Maximum escape transition length is:
//
// COLOR_RESET "%xn"
// + COLOR_INTENSE "%xh"
// + COLOR_UNDERLINE "%xu"
// + COLOR_BLINK "%xf"
// + COLOR_INVERSE "%xi"
2010-08-09 15:26:32 -07:00
// + COLOR_FG_... "%x<#FFFFFF>"
// + COLOR_BG_... "%x<#FFFFFF>"
//
2010-08-09 15:26:32 -07:00
// Each of the five codes is 3 bytes, and two codes are 11 bytes, or 37 bytes total.
//
2010-08-09 15:26:32 -07:00
#define COLOR_MAXIMUM_ESCAPE_TRANSITION_LENGTH 37
// Generate the minimal color %-sequence that will transition from one color state
// to another.
//
static UTF8 *ColorTransitionEscape
(
ColorState csCurrent,
ColorState csNext,
size_t *nTransition
)
{
ValidateColorState(csCurrent);
ValidateColorState(csNext);
static UTF8 Buffer[COLOR_MAXIMUM_ESCAPE_TRANSITION_LENGTH+1];
if (csCurrent == csNext)
{
*nTransition = 0;
Buffer[0] = '\0';
return Buffer;
}
size_t i = 0;
// Do we need to go through the normal state?
//
if ( ((csCurrent & ~csNext) & CS_ATTRS)
|| ( (csNext & CS_BACKGROUND) == CS_BG_DEFAULT
&& (csCurrent & CS_BACKGROUND) != CS_BG_DEFAULT)
|| ( (csNext & CS_FOREGROUND) == CS_FG_DEFAULT
&& (csCurrent & CS_FOREGROUND) != CS_FG_DEFAULT))
{
memcpy(Buffer + i, aColors[COLOR_INDEX_RESET].pEscape, aColors[COLOR_INDEX_RESET].nEscape);
i += aColors[COLOR_INDEX_RESET].nEscape;
csCurrent = CS_NORMAL;
}
ColorState tmp = csCurrent ^ csNext;
if (CS_ATTRS & tmp)
{
for (unsigned int iAttr = COLOR_INDEX_ATTR; iAttr < COLOR_INDEX_FG; iAttr++)
{
if (aColors[iAttr].cs == (aColors[iAttr].csMask & tmp))
{
memcpy(Buffer + i, aColors[iAttr].pEscape, aColors[iAttr].nEscape);
i += aColors[iAttr].nEscape;
}
}
}
RGB rgb;
unsigned int iColor;
if (CS_FOREGROUND & tmp)
{
if (CS_FG_INDEXED & csNext)
{
2010-08-11 18:15:00 -07:00
iColor = COLOR_INDEX_FG + static_cast<unsigned int>(CS_FG_FIELD(csNext));
2010-08-11 15:50:24 -07:00
if (iColor < COLOR_INDEX_FG + COLOR_INDEX_DEFAULT)
{
memcpy(Buffer + i, aColors[iColor].pEscape, aColors[iColor].nEscape);
i += aColors[iColor].nEscape;
}
}
else
{
cs2rgb(CS_FG_FIELD(csNext), &rgb);
2010-08-11 15:50:24 -07:00
mux_sprintf(Buffer + i, 12, T("%%x<#%02X%02X%02X>"), rgb.r, rgb.g, rgb.b);
i += 11;
}
}
if (CS_BACKGROUND & tmp)
{
if (CS_BG_INDEXED & csNext)
{
2010-08-11 18:15:00 -07:00
iColor = COLOR_INDEX_BG + static_cast<unsigned int>(CS_BG_FIELD(csNext));
2010-08-11 15:50:24 -07:00
if (iColor < COLOR_INDEX_BG + COLOR_INDEX_DEFAULT)
{
memcpy(Buffer + i, aColors[iColor].pEscape, aColors[iColor].nEscape);
i += aColors[iColor].nEscape;
}
}
else
{
cs2rgb(CS_BG_FIELD(csNext), &rgb);
mux_sprintf(Buffer + i, 12, T("%%X<#%02X%02X%02X>"), rgb.r, rgb.g, rgb.b);
2010-08-11 15:50:24 -07:00
i += 11;
}
}
Buffer[i] = '\0';
*nTransition = i;
return Buffer;
}
// Maximum ANSI transition length is:
//
// ANSI_NORMAL "\033[0m"
// + ANSI_HILITE "\033[1m"
// + ANSI_UNDER "\033[4m"
// + ANSI_BLINK "\033[5m"
// + ANSI_INVERSE "\033[7m"
2010-08-09 15:26:32 -07:00
// + ANSI_FG_... "\033[38;5;255m"
// + ANSI_BWHITE "\033[48;5;255m"
//
2010-08-09 15:26:32 -07:00
// Five of the seven codes are 4 bytes, and two are 11 bytes, or 42 bytes total.
//
2010-08-09 15:26:32 -07:00
#define COLOR_MAXIMUM_ANSI_TRANSITION_LENGTH 42
// Generate the minimal ANSI sequence that will transition from one color state
// to another.
//
static UTF8 *ColorTransitionANSI
(
ColorState &csClient,
ColorState csNext,
size_t *nTransition,
bool fNoBleed,
bool fColor256
)
{
ValidateColorState(csClient);
ValidateColorState(csNext);
static UTF8 Buffer[COLOR_MAXIMUM_ANSI_TRANSITION_LENGTH+1];
// First, modify our sense of what 'normal' is.
//
if ( fNoBleed
&& (csNext & CS_FOREGROUND) == CS_FG_DEFAULT)
{
csNext = (csNext & ~CS_FOREGROUND) | CS_FG_WHITE;
}
// Approximate Foreground Color
//
RGB rgb;
unsigned int iColor;
if (fColor256)
{
if (CS_FG_INDEXED & csNext)
{
if (CS_FG_FIELD(csNext) < COLOR_INDEX_DEFAULT)
{
iColor = COLOR_INDEX_FG + static_cast<unsigned int>(CS_FG_FIELD(csNext));
}
else
{
iColor = COLOR_INDEX_FG + COLOR_INDEX_DEFAULT;
}
}
else
{
cs2rgb(CS_FG_FIELD(csNext), &rgb);
iColor = COLOR_INDEX_FG + FindNearestPaletteEntry(rgb, true);
}
}
else
{
if (CS_FG_INDEXED & csNext)
{
if (CS_FG_FIELD(csNext) < COLOR_INDEX_DEFAULT)
{
iColor = COLOR_INDEX_FG + palette[CS_FG_FIELD(csNext)].color16;
}
else
{
iColor = COLOR_INDEX_FG + COLOR_INDEX_DEFAULT;
}
}
else
{
cs2rgb(CS_FG_FIELD(csNext), &rgb);
iColor = COLOR_INDEX_FG + FindNearestPaletteEntry(rgb, false);
}
// For foreground 256-to-16-color down-conversion, we 'borrow' the
// highlite capability. We don't need or want it if the client supports
// 256-color, and there is no highlite capability we can borrow for
// background color. This decision is a prerequisite to the 'return
// to normal' decision below.
//
if (COLOR_INDEX_FG + 8 <= iColor && iColor <= COLOR_INDEX_FG + 15)
{
csNext |= CS_INTENSE;
iColor -= 8;
}
}
2010-08-11 15:50:24 -07:00
if (iColor < COLOR_INDEX_FG + COLOR_INDEX_DEFAULT)
{
csNext = UpdateColorState(csNext, iColor);
}
// Aproximate Background Color
//
if (fColor256)
{
if (CS_BG_INDEXED & csNext)
{
if (CS_BG_FIELD(csNext) < COLOR_INDEX_DEFAULT)
{
iColor = COLOR_INDEX_BG + static_cast<unsigned int>(CS_BG_FIELD(csNext));
}
else
{
iColor = COLOR_INDEX_BG + COLOR_INDEX_DEFAULT;
}
}
else
{
cs2rgb(CS_BG_FIELD(csNext), &rgb);
iColor = COLOR_INDEX_BG + FindNearestPaletteEntry(rgb, true);
}
}
else
{
if (CS_BG_INDEXED & csNext)
{
if (CS_BG_FIELD(csNext) < COLOR_INDEX_DEFAULT)
{
iColor = COLOR_INDEX_BG + palette[CS_BG_FIELD(csNext)].color8;
}
else
{
iColor = COLOR_INDEX_BG + COLOR_INDEX_DEFAULT;
}
}
else
{
cs2rgb(CS_BG_FIELD(csNext), &rgb);
iColor = COLOR_INDEX_BG + FindNearestPalette8Entry(rgb);
}
}
2010-08-11 15:50:24 -07:00
if (iColor < COLOR_INDEX_BG + COLOR_INDEX_DEFAULT)
{
csNext = UpdateColorState(csNext, iColor);
}
if (csClient == csNext)
{
*nTransition = 0;
Buffer[0] = '\0';
return Buffer;
}
size_t i = 0;
// Do we need to go through the normal state?
//
if ( ((csClient & ~csNext) & CS_ATTRS)
|| ( (csNext & CS_BACKGROUND) == CS_BG_DEFAULT
&& (csClient & CS_BACKGROUND) != CS_BG_DEFAULT)
|| ( (csNext & CS_FOREGROUND) == CS_FG_DEFAULT
&& (csClient & CS_FOREGROUND) != CS_FG_DEFAULT))
{
memcpy(Buffer + i, ANSI_NORMAL, sizeof(ANSI_NORMAL)-1);
i += sizeof(ANSI_NORMAL)-1;
csClient = CS_NORMAL;
}
ColorState tmp = csClient ^ csNext;
if (CS_ATTRS & tmp)
{
for (unsigned int iAttr = COLOR_INDEX_ATTR; iAttr < COLOR_INDEX_FG; iAttr++)
{
if ( g_no_flash
&& COLOR_INDEX_BLINK == iAttr)
{
continue;
}
if (aColors[iAttr].cs == (aColors[iAttr].csMask & tmp))
{
memcpy(Buffer + i, aColors[iAttr].pAnsi, aColors[iAttr].nAnsi);
i += aColors[iAttr].nAnsi;
}
}
}
// At this point, all colors are indexed.
//
if (CS_FOREGROUND & tmp)
{
2010-08-11 18:15:00 -07:00
iColor = COLOR_INDEX_FG + static_cast<unsigned int>(CS_FG_FIELD(csNext));
if (iColor < COLOR_INDEX_FG + COLOR_INDEX_DEFAULT)
{
memcpy(Buffer + i, aColors[iColor].pAnsi, aColors[iColor].nAnsi);
i += aColors[iColor].nAnsi;
}
}
if (CS_BACKGROUND & tmp)
{
2010-08-11 18:15:00 -07:00
iColor = COLOR_INDEX_BG + static_cast<unsigned int>(CS_BG_FIELD(csNext));
if (iColor < COLOR_INDEX_BG + COLOR_INDEX_DEFAULT)
{
memcpy(Buffer + i, aColors[iColor].pAnsi, aColors[iColor].nAnsi);
i += aColors[iColor].nAnsi;
}
}
Buffer[i] = '\0';
*nTransition = i;
csClient = csNext;
return Buffer;
}
feat(#2136): flip fargs to const UTF8 * const — and convert every site the compiler surfaced The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module interfaces take `const UTF8 * const fargs[]`. Double-const is load-bearing: C++ qualification conversion needs const at both pointer levels, so builder-side `UTF8 *[]` arrays convert implicitly — the evaluator, the JIT marshaller, and every owner site need zero casts, and slot reassignment inside bodies becomes a compile error for free. The conversions: the flip landed first so the compiler enumerated every violation; this commit is that inventory worked to zero — ~250 sites across funceval, funceval2, functions, funmath, help, mail, session, powers, levels, predicates, conf, walkdb, stringutil, timeutil/ date_scan (regenerated, one-line diff), exp3, and mux_main, each classified per docs/campaign-2136-const-fargs.md's four recipes. New idioms (functions.h): trim_space_sep_n() — non-destructive trim for (pointer, length) consumers, so trim-then-scan sites need no copy at all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers. countwords() and DecodeListOfIntegers() rewritten non-destructive. The flip deleted more than it added: #2157's fun_munge list1 copy, the engine_com help-topic copy, fun_index's in-place NUL write, and five const_casts (process_sex x4, sha1_helper). const_cast budget: zero added. Trap recorded in the brief: an old-signature definition doesn't fail the build — it becomes a C++ overload, and the new-signature symbol stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge bridges, the dbt_spike stub, and exp3::Call were all silently shadowed; muxscript was the only host that noticed, because netmux's own net.cpp resolved the flat-namespace lookup. After any signature flip, grep the old spelling. Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and make test-scenario, including the new tests/scenario/sidefx_fargs.py that live-probes the class-3 wrappers smoke never touches (pemit/ trigger/link/tel/wipe/destroy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:34:23 -06:00
void LettersToColorState(ColorState &cs, const UTF8 *pIn)
{
cs = CS_NORMAL;
bool fBackground = false;
size_t i = 0;
while ('\0' != pIn[i])
{
unsigned int iColor;
unsigned ch = pIn[i];
if ('<' == ch)
{
i++;
size_t j = i;
while ( '\0' != (ch = pIn[i])
&& '>' != ch)
{
i++;
}
RGB rgb;
if ('>' == ch)
{
if (parse_rgb(i - j, pIn + j, rgb))
2010-08-11 15:50:24 -07:00
{
iColor = FindNearestPaletteEntry(rgb, true);
bool fExact = ( palette[iColor].rgb.r == rgb.r
&& palette[iColor].rgb.g == rgb.g
&& palette[iColor].rgb.b == rgb.b);
if (fBackground)
{
cs = (cs & ~CS_BACKGROUND) | (fExact ? CS_BG(iColor) : (rgb2cs(&rgb) << 32));
}
else
{
cs = (cs & ~CS_FOREGROUND) | (fExact ? CS_FG(iColor) : rgb2cs(&rgb));
}
2010-08-11 15:50:24 -07:00
}
i++;
}
fBackground = false;
}
else if ('/' == ch)
{
fBackground = true;
i++;
}
else
{
iColor = ColorTable[pIn[i]];
if (iColor)
{
cs = UpdateColorState(cs, iColor);
}
fBackground = false;
i++;
}
}
}
feat(#2136): flip fargs to const UTF8 * const — and convert every site the compiler surfaced The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module interfaces take `const UTF8 * const fargs[]`. Double-const is load-bearing: C++ qualification conversion needs const at both pointer levels, so builder-side `UTF8 *[]` arrays convert implicitly — the evaluator, the JIT marshaller, and every owner site need zero casts, and slot reassignment inside bodies becomes a compile error for free. The conversions: the flip landed first so the compiler enumerated every violation; this commit is that inventory worked to zero — ~250 sites across funceval, funceval2, functions, funmath, help, mail, session, powers, levels, predicates, conf, walkdb, stringutil, timeutil/ date_scan (regenerated, one-line diff), exp3, and mux_main, each classified per docs/campaign-2136-const-fargs.md's four recipes. New idioms (functions.h): trim_space_sep_n() — non-destructive trim for (pointer, length) consumers, so trim-then-scan sites need no copy at all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers. countwords() and DecodeListOfIntegers() rewritten non-destructive. The flip deleted more than it added: #2157's fun_munge list1 copy, the engine_com help-topic copy, fun_index's in-place NUL write, and five const_casts (process_sex x4, sha1_helper). const_cast budget: zero added. Trap recorded in the brief: an old-signature definition doesn't fail the build — it becomes a C++ overload, and the new-signature symbol stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge bridges, the dbt_spike stub, and exp3::Call were all silently shadowed; muxscript was the only host that noticed, because netmux's own net.cpp resolved the flat-namespace lookup. After any signature flip, grep the old spelling. Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and make test-scenario, including the new tests/scenario/sidefx_fargs.py that live-probes the class-3 wrappers smoke never touches (pemit/ trigger/link/tel/wipe/destroy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:34:23 -06:00
UTF8 *LettersToBinary(const UTF8 *pLetters)
{
size_t n;
ColorState cs = CS_NORMAL;
LettersToColorState(cs, pLetters);
return ColorTransitionBinary(CS_NORMAL, cs, &n);
}
enum class HTMLtag
{
kIntense,
kUnderline,
kBlink,
kInverse,
kColor,
kNormal,
};
typedef struct
{
bool fBeginEnd;
HTMLtag kTag;
ColorState cs;
2010-08-14 20:55:04 -07:00
int iStart;
} tag_node;
UTF8 *convert_to_html(const UTF8 *pString)
{
// Converting to HTML is performed in two passes. The first pass
// determines an optimal nesting order of tags. The second pass generates
// the HTML.
//
2010-08-14 20:55:04 -07:00
tag_node List[(LBUF_SIZE-1)/3];
int nList = 0;
int Stack[10];
int nStack = 0;
HTMLtag tagmap[5] = {HTMLtag::kIntense, HTMLtag::kUnderline, HTMLtag::kBlink, HTMLtag::kInverse, HTMLtag::kColor };
2010-08-14 20:55:04 -07:00
int iCopy;
int i = 0;
ColorState csPrev = CS_NORMAL;
ColorState csNext = CS_NORMAL;
unsigned int iCode = COLOR_NOTCOLOR;
if ('\0' != pString[i])
{
iCode = mux_color(pString + i);
if (COLOR_NOTCOLOR == iCode)
{
List[nList].fBeginEnd = true;
List[nList].kTag = HTMLtag::kNormal;
List[nList].cs = CS_NORMAL;
2010-08-14 20:55:04 -07:00
List[nList].iStart = -1;
nList++;
}
}
while ( '\0' != pString[i]
|| csNext != csPrev)
{
iCopy = i;
while ( '\0' != pString[i]
&& COLOR_NOTCOLOR == iCode)
{
i += utf8_advance_nul(pString + i);
iCode = mux_color(pString + i);
}
2010-08-14 20:55:04 -07:00
while (csNext != csPrev)
{
for (unsigned int iAttr = COLOR_INDEX_ATTR; iAttr < COLOR_INDEX_FG + 1; iAttr++)
{
ColorState tmp = csNext ^ csPrev;
HTMLtag kNext = tagmap[iAttr - COLOR_INDEX_ATTR];
bool fOpen = false;
bool fClose = false;
if (iAttr < COLOR_INDEX_FG)
{
// Attribute
//
if (tmp & aColors[iAttr].csMask)
{
if (csNext & aColors[iAttr].csMask)
{
fOpen = true;
}
else
{
fClose = true;
}
}
}
else
{
// Color
//
if (tmp & CS_FOREGROUND)
{
if ( (csPrev & CS_FOREGROUND) == CS_FG_DEFAULT
&& (csNext & CS_FOREGROUND) != CS_FG_DEFAULT)
{
fOpen = true;
}
else if ( (csPrev & CS_FOREGROUND) != CS_FG_DEFAULT
&& (csNext & CS_FOREGROUND) == CS_FG_DEFAULT)
{
fClose = true;
}
else
{
fOpen = true;
fClose = true;
}
}
if (tmp & CS_BACKGROUND)
{
if ( (csPrev & CS_BACKGROUND) == CS_BG_DEFAULT
&& (csNext & CS_BACKGROUND) != CS_BG_DEFAULT)
{
fOpen = true;
}
else if ( (csPrev & CS_BACKGROUND) != CS_BG_DEFAULT
&& (csNext & CS_BACKGROUND) == CS_BG_DEFAULT)
{
fClose = true;
}
else
{
fOpen = true;
fClose = true;
}
}
}
if (fClose)
{
for (int j = nStack - 1; 0 <= j; j--)
{
if (List[Stack[j]].kTag == kNext)
{
2010-08-14 20:55:04 -07:00
if (List[Stack[j]].iStart != List[Stack[nStack-1]].iStart)
{
2010-08-14 20:55:04 -07:00
// Pop other tags and mark in csPrev that they are gone. We'll add them back in the next
// iteration of the loop. We stop at the point where tags can be swapped with the one
// we're interested in.
//
for (int k = nStack - 1; j < k && List[Stack[j]].iStart != List[Stack[nStack-1]].iStart; k--)
{
List[nList] = List[Stack[k]];
List[nList].fBeginEnd = false;
2010-08-14 20:55:04 -07:00
List[nList].iStart = -1;
2010-08-14 20:55:04 -07:00
switch (List[nList].kTag)
{
case HTMLtag::kIntense:
csPrev &= ~CS_INTENSE;
break;
case HTMLtag::kUnderline:
csPrev &= ~CS_UNDERLINE;
break;
case HTMLtag::kBlink:
csPrev &= ~CS_BLINK;
break;
case HTMLtag::kInverse:
csPrev &= ~CS_INVERSE;
break;
case HTMLtag::kColor:
csPrev = (csPrev & ~(CS_FOREGROUND|CS_BACKGROUND)) | CS_NORMAL;
break;
2013-09-10 20:07:52 -06:00
default:
break;
}
nList++;
2010-08-14 20:55:04 -07:00
nStack--;
}
}
2010-08-14 20:55:04 -07:00
if (j != nStack - 1)
{
// Change the order of tags which open at the
// same position so that the one we want to
// close can be popped.
//
tag_node t = List[Stack[j]];
List[Stack[j]] = List[Stack[nStack-1]];
List[Stack[nStack-1]] = t;
j = nStack - 1;
}
List[nList] = List[Stack[j]];
List[nList].fBeginEnd = false;
List[nList].iStart = -1;
switch (List[nList].kTag)
{
case HTMLtag::kIntense:
2010-08-14 20:55:04 -07:00
csPrev &= ~CS_INTENSE;
break;
case HTMLtag::kUnderline:
2010-08-14 20:55:04 -07:00
csPrev &= ~CS_UNDERLINE;
break;
case HTMLtag::kBlink:
2010-08-14 20:55:04 -07:00
csPrev &= ~CS_BLINK;
break;
case HTMLtag::kInverse:
2010-08-14 20:55:04 -07:00
csPrev &= ~CS_INVERSE;
break;
case HTMLtag::kColor:
2010-08-14 20:55:04 -07:00
csPrev = (csPrev & ~(CS_FOREGROUND|CS_BACKGROUND)) | CS_NORMAL;
break;
2013-09-10 20:07:52 -06:00
default:
break;
2010-08-14 20:55:04 -07:00
}
nList++;
nStack--;
if (0 == nStack)
{
List[nList].fBeginEnd = true;
List[nList].kTag = HTMLtag::kNormal;
List[nList].cs = CS_NORMAL;
2010-08-14 20:55:04 -07:00
List[nList].iStart = -1;
nList++;
}
break;
}
}
}
if (fOpen)
{
List[nList].fBeginEnd = true;
List[nList].cs = csNext;
List[nList].kTag = kNext;
2010-08-14 20:55:04 -07:00
List[nList].iStart = -1;
switch (List[nList].kTag)
{
case HTMLtag::kIntense:
2010-08-14 20:55:04 -07:00
csPrev |= CS_INTENSE;
break;
case HTMLtag::kUnderline:
2010-08-14 20:55:04 -07:00
csPrev |= CS_UNDERLINE;
break;
case HTMLtag::kBlink:
2010-08-14 20:55:04 -07:00
csPrev |= CS_BLINK;
break;
case HTMLtag::kInverse:
2010-08-14 20:55:04 -07:00
csPrev |= CS_INVERSE;
break;
case HTMLtag::kColor:
2010-08-14 20:55:04 -07:00
csPrev &= ~(CS_FOREGROUND|CS_BACKGROUND);
csPrev |= (CS_FOREGROUND|CS_BACKGROUND) & csNext;;
break;
2013-09-10 20:07:52 -06:00
default:
break;
2010-08-14 20:55:04 -07:00
}
Stack[nStack++] = nList++;
}
}
2010-08-14 20:55:04 -07:00
}
2010-08-14 20:55:04 -07:00
if (0 < nList)
{
List[nList-1].iStart = iCopy;
}
while ( '\0' != pString[i]
&& COLOR_NOTCOLOR != iCode)
{
csNext = UpdateColorState(csNext, iCode, pString + i);
i += utf8_advance_nul(pString + i);
iCode = mux_color(pString + i);
}
// When CS_INTENSE is active and the foreground is a basic ANSI
// color (0-7), substitute the bright palette entry (8-15) so
// that HTML <COLOR> tags carry the correct RGB value.
//
if ( (csNext & CS_INTENSE)
&& (CS_FG_INDEXED & csNext))
{
unsigned int idx = static_cast<unsigned int>(CS_FG_FIELD(csNext));
if (idx < 8)
{
csNext = (csNext & ~CS_FOREGROUND) | CS_FG(idx + 8);
}
}
}
static UTF8 aBuffer[2*LBUF_SIZE];
UTF8 *pBuffer = aBuffer;
for (int iList = 0; iList < nList; iList++)
{
if (HTMLtag::kNormal != List[iList].kTag)
{
*pBuffer++ = '<';
if (!List[iList].fBeginEnd)
{
*pBuffer++ = '/';
}
switch (List[iList].kTag)
{
case HTMLtag::kIntense:
2010-08-14 20:55:04 -07:00
*pBuffer++ = 'B';
break;
case HTMLtag::kUnderline:
*pBuffer++ = 'U';
break;
case HTMLtag::kBlink:
2010-08-14 20:55:04 -07:00
*pBuffer++ = 'I';
break;
case HTMLtag::kInverse:
2010-08-14 20:55:04 -07:00
*pBuffer++ = 'S';
break;
case HTMLtag::kColor:
if (List[iList].fBeginEnd)
{
ColorState cs = List[iList].cs;
if ( (CS_FOREGROUND & cs) != CS_FG_DEFAULT
&& (CS_FG_INDEXED & cs))
{
cs = (cs & ~CS_FOREGROUND) | rgb2cs(&palette[CS_FG_FIELD(cs)].rgb);
}
if ( (CS_BACKGROUND & cs) != CS_BG_DEFAULT
&& (CS_BG_INDEXED & cs))
{
cs = (cs & ~CS_BACKGROUND) | (rgb2cs(&palette[CS_BG_FIELD(cs)].rgb) << 32);
}
if ((CS_FOREGROUND & cs) == CS_FG_DEFAULT)
{
if ((CS_BACKGROUND & cs) != CS_BG_DEFAULT)
{
mux_sprintf(pBuffer, sizeof(aBuffer) - (pBuffer - aBuffer) - 1, T("COLOR BACK=#%06llX"), CS_BG_FIELD(cs));
pBuffer += strlen(reinterpret_cast<char *>(pBuffer));
}
}
else
{
if ((CS_BACKGROUND & cs) == CS_BG_DEFAULT)
{
mux_sprintf(pBuffer, sizeof(aBuffer) - (pBuffer - aBuffer) - 1, T("COLOR #%06llX"), CS_FG_FIELD(cs));
pBuffer += strlen(reinterpret_cast<char *>(pBuffer));
}
else
{
mux_sprintf(pBuffer, sizeof(aBuffer) - (pBuffer - aBuffer) - 1, T("COLOR #%06llX #%06llX"), CS_FG_FIELD(cs), CS_BG_FIELD(cs));
pBuffer += strlen(reinterpret_cast<char *>(pBuffer));
}
}
}
else
{
mux_sprintf(pBuffer, sizeof(aBuffer) - (pBuffer - aBuffer) - 1, T("COLOR"));
pBuffer += strlen(reinterpret_cast<char *>(pBuffer));
}
break;
2013-09-10 20:07:52 -06:00
default:
break;
}
*pBuffer++ = '>';
}
2010-08-13 21:51:01 -07:00
iCopy = i = List[iList].iStart;
2010-08-14 20:55:04 -07:00
if (0 <= iCopy)
2010-08-13 21:51:01 -07:00
{
2010-08-14 20:55:04 -07:00
if ('\0' != pString[i])
{
iCode = mux_color(pString + i);
}
while ( '\0' != pString[i]
&& COLOR_NOTCOLOR == iCode)
{
i += utf8_advance_nul(pString + i);
2010-08-14 20:55:04 -07:00
iCode = mux_color(pString + i);
}
size_t n = i - iCopy;
2010-08-14 20:55:04 -07:00
if (0 < n)
{
memcpy(pBuffer, pString + List[iList].iStart, n);
pBuffer += n;
}
}
}
*pBuffer = '\0';
return aBuffer;
}
/*! \brief Convert private color code points within a string to ANSI color
* sequences.
*
* This routine converts these internal color code points to ANSI sequences
* which clients will recognize.
*
* Internally, the server uses color code points to represent color. This
* representation is more compact, it simplifies the parser, and it thwarts
* the possibility of softcode generating naked ESC characters. Several
* protocols use the ESC character, so it is helpful if the server controls
* their use.
*
* \param pString String (length LBUF_SIZE-1 or less) with color code points.
* \param fNoBleed Should we dodge a naked return-to-normal state.
* \param fColor256 Can we use the full 256-color palette?
* \return String with exportable ANSI color sequences.
*/
2025-03-24 14:53:29 -06:00
UTF8 *convert_color(const UTF8 *pString, bool fNoBleed, bool fColor256)
{
static UTF8 aBuffer[2*LBUF_SIZE];
static const UTF8 *pNormal= T(ANSI_NORMAL ANSI_WHITE);
size_t nNormal = sizeof(ANSI_NORMAL)-1;
if (fNoBleed)
{
nNormal += sizeof(ANSI_WHITE)-1;
}
ColorState csClient = (fNoBleed)?CS_FG_WHITE:CS_NORMAL;
ColorState csPrev = CS_NORMAL;
ColorState csNext = CS_NORMAL;
UTF8 *pBuffer = aBuffer;
size_t i = 0;
size_t iCopy = 0;
unsigned int iCode = COLOR_NOTCOLOR;
if ('\0' != pString[i])
{
iCode = mux_color(pString + i);
}
while ('\0' != pString[i])
{
iCopy = i;
while ( '\0' != pString[i]
&& COLOR_NOTCOLOR == iCode)
{
i += utf8_advance_nul(pString + i);
iCode = mux_color(pString + i);
}
if (iCopy < i)
{
if (csNext != csPrev)
{
2018-10-03 17:54:51 +00:00
UTF8 *pTransition = nullptr;
size_t nTransition;
pTransition = ColorTransitionANSI( csClient, csNext,
&nTransition, fNoBleed, fColor256);
if (nTransition)
{
if (sizeof(aBuffer) - (pBuffer - aBuffer) - nNormal - 1 < nTransition)
{
break;
}
memcpy(pBuffer, pTransition, nTransition);
pBuffer += nTransition;
}
csPrev = csNext;
}
if (sizeof(aBuffer) - (pBuffer - aBuffer) - nNormal - 1 < i - iCopy)
{
break;
}
memcpy(pBuffer, pString + iCopy, i - iCopy);
pBuffer += i - iCopy;
iCopy = i;
}
while ( '\0' != pString[i]
&& COLOR_NOTCOLOR != iCode)
{
csNext = UpdateColorState(csNext, iCode, pString + i);
i += utf8_advance_nul(pString + i);
iCode = mux_color(pString + i);
}
}
if (csPrev != CS_NORMAL)
{
memcpy(pBuffer, pNormal, nNormal);
pBuffer += nNormal;
}
*pBuffer = '\0';
return aBuffer;
}
2025-03-24 14:53:29 -06:00
UTF8 *strip_color(const UTF8 *pString, size_t *pnBytes, size_t *pnPoints)
{
static UTF8 aBuffer[LBUF_SIZE];
UTF8 *pBuffer = aBuffer;
2018-10-03 17:54:51 +00:00
if (nullptr == pString)
{
2018-10-03 17:54:51 +00:00
if (nullptr != pnBytes)
{
*pnBytes = 0;
}
2018-10-03 17:54:51 +00:00
if (nullptr != pnPoints)
{
*pnPoints = 0;
}
*pBuffer = '\0';
return aBuffer;
}
size_t nPoints = 0;
while ('\0' != *pString)
{
if (COLOR_NOTCOLOR == mux_color(pString))
{
utf8_safe_chr(pString, aBuffer, &pBuffer);
nPoints++;
}
pString += utf8_advance_nul(pString);
}
*pBuffer = '\0';
2018-10-03 17:54:51 +00:00
if (nullptr != pnBytes)
{
*pnBytes = pBuffer - aBuffer;
}
2018-10-03 17:54:51 +00:00
if (nullptr != pnPoints)
{
*pnPoints = nPoints;
}
return aBuffer;
}
typedef struct
{
int len;
const UTF8 *p;
} LITERAL_STRING_STRUCT;
#define NUM_WS_SUBS 6
#define NUM_MU_SUBS 14
static LITERAL_STRING_STRUCT MU_Substitutes[NUM_MU_SUBS] =
{
{ 1, T(" ") }, // 0
{ 1, T(" ") }, // 1
{ 2, T("%t") }, // 2
{ 2, T("%r") }, // 3
fix(lib): safe_copy_buf must not pass a null pointer to memcpy (#1458) UBSan reports "null pointer passed as argument 2" at stringutil.cpp:4348. Passing null to memcpy is UB even when the length is zero -- the standard requires valid pointers regardless (C17 7.24.1p2). The caller was not obvious from the report, so I instrumented safe_copy_buf on Windows to capture a symbolized stack whenever src was null and ran the smoke suite. It fires 5 times, always with nLen == 0, always from the same place: translate_string stringutil.cpp:3643 eval_ecall jit_compiler.cpp:4456 dbt_run dbt.cpp:641 run_cached_program jit_compiler.cpp:2854 jit_eval jit_compiler.cpp:5190 mux_exec ast.cpp:2928 The source is a data table, not a computation. MU_EscapeConvert maps 0x0D (CR) to substitute 4, and MU_Substitutes[4] was { 0, nullptr } -- the deliberate "emit nothing" slot. translate_string hands it straight to safe_copy_buf, which hands it to memcpy. So every CR that passes through translate_string is one of these, which is why it reproduces readily under a sanitizer and never misbehaves without one. Fixed at both ends: - MU_Substitutes[4] is now { 0, T("") }. A null in a table of string literals is a trap for anyone else who indexes it; empty says the same thing and cannot be dereferenced. - safe_copy_buf returns early on a null src. Its siblings safe_copy_str, safe_copy_str_lbuf and utf8_safe_chr all already do this, so tolerating null is the established convention among these helpers and safe_copy_buf was the lone outlier -- worth closing across all 63 call sites rather than only the one that trips today. It also covers TrimPartialSequence, which would dereference src outright had a caller passed null with a non-zero length. No behaviour change: both spellings emit zero bytes. Verified on Windows Server 2022, MSVC 14.51, Release x64. Clean rebuild, 0 warnings, 0 errors. Smoke 316 dispatched / 1487 succeeded / 17 failed -- identical to the pre-change baseline, the 17 being the known build-config set (exp3, UNIX_DIGEST, REALITY_LVLS). The behaviour is already pinned by translate_fn.mux TC004, which asserts translate(%r,p) == %r -- %r is CRLF, so that case consumes substitute 4 (CR, emit nothing) and substitute 3 (LF, "%r"). It still passes, so no new test is needed. Confirmed live as well: @decompile of an attribute holding alpha%rbeta%rgamma returns it unchanged. Windows has no UBSan, so this cannot verify the report itself is gone -- that needs the Linux --enable-sanitizers build from #1449. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:45:52 -06:00
{ 0, T("") }, // 4 -- emit nothing (\r); empty, not null (#1458)
{ 2, T("%b") }, // 5
{ 2, T("%%") }, // 6
{ 2, T("%(") }, // 7
{ 2, T("%)") }, // 8
{ 2, T("%[") }, // 9
{ 2, T("%]") }, // 10
{ 2, T("%{") }, // 11
{ 2, T("%}") }, // 12
{ 2, T("\\\\") } // 13
};
const unsigned char MU_EscapeConvert[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 4, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 0, 0, 0, 0, 6, 0, 0, 7, 8, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,13,10, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,11, 0,12, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
const unsigned char MU_EscapeNoConvert[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
// Convert raw character sequences into MUX substitutions (type = 1)
// or strips them (type = 0).
//
2025-03-24 14:53:29 -06:00
UTF8 *translate_string(const UTF8 *pString, bool bConvert)
{
static UTF8 szTranslatedString[LBUF_SIZE];
UTF8 *pTranslatedString = szTranslatedString;
if (!pString)
{
*pTranslatedString = '\0';
return szTranslatedString;
}
ColorState csCurrent = CS_NOBLEED;
ColorState csPrevious = csCurrent;
const UTF8 *MU_EscapeChar = (bConvert)? MU_EscapeConvert : MU_EscapeNoConvert;
while ('\0' != *pString)
{
unsigned int iCode = mux_color(pString);
if (COLOR_NOTCOLOR == iCode)
{
size_t nTransition = 0;
if (bConvert)
{
UTF8 *pTransition = ColorTransitionEscape(csPrevious, csCurrent, &nTransition);
safe_str(pTransition, szTranslatedString, &pTranslatedString);
csPrevious = csCurrent;
}
UTF8 ch = pString[0];
unsigned char code = MU_EscapeChar[ch];
if ( 0 < code
&& code < NUM_MU_SUBS)
{
if ( ' ' == ch
&& ' ' == pString[1])
{
code = 5;
}
safe_copy_buf(MU_Substitutes[code].p,
MU_Substitutes[code].len, szTranslatedString,
&pTranslatedString);
}
else
{
utf8_safe_chr(pString, szTranslatedString, &pTranslatedString);
}
}
else
{
csCurrent = UpdateColorState(csCurrent, iCode, pString);
}
pString += utf8_advance_nul(pString);
}
*pTranslatedString = '\0';
return szTranslatedString;
}
bool IsDecompFriendly(const UTF8 *pString)
{
bool fFriendly = true;
if (!pString)
{
return fFriendly;
}
const UTF8 *MU_EscapeChar = MU_EscapeConvert;
while ( '\0' != *pString
&& fFriendly)
{
unsigned int iCode = mux_color(pString);
if (COLOR_NOTCOLOR == iCode)
{
UTF8 ch = pString[0];
unsigned char code = MU_EscapeChar[ch];
if ( 0 < code
&& code < NUM_WS_SUBS)
{
if (' ' == ch)
{
if (' ' == pString[1])
{
// Two adjacent spaces are not @decomp-friendly.
//
fFriendly = false;
break;
}
// An isolated space is still @decomp-friendly.
//
}
else
{
// Raw tabs and newlines are not @decomp-friendly.
//
fFriendly = false;
break;
}
}
}
else
{
// Raw color codes are not @decomp-friendly.
//
fFriendly = false;
break;
}
pString += utf8_advance_nul(pString);
}
return fFriendly;
}
/* ---------------------------------------------------------------------------
* munge_space: Compress multiple spaces to one space, also remove leading and
* trailing spaces.
*/
2025-03-24 14:53:29 -06:00
UTF8 *munge_space(const UTF8 *string)
{
UTF8 *buffer = alloc_lbuf("munge_space");
const UTF8 *p = string;
UTF8 *q = buffer;
if (p)
{
// Remove initial spaces.
//
while (mux_isspace(*p))
p++;
while (*p)
{
while (*p && !mux_isspace(*p))
*q++ = *p++;
while (mux_isspace(*p))
{
p++;
}
if (*p)
*q++ = ' ';
}
}
// Remove terminal spaces and terminate string.
//
*q = '\0';
return buffer;
}
/* ---------------------------------------------------------------------------
* trim_spaces: Remove leading and trailing spaces and space-compress internal
* spaces.
*/
2025-03-24 14:53:29 -06:00
UTF8 *trim_spaces(const UTF8 *string)
{
UTF8 *buffer = alloc_lbuf("trim_spaces");
const UTF8 *p = string;
UTF8 *q = buffer;
if (p)
{
// Remove initial spaces.
//
while (mux_isspace(*p))
{
p++;
}
while (*p)
{
// Copy non-space characters.
//
while (*p && !mux_isspace(*p))
{
*q++ = *p++;
}
// Compress spaces.
//
while (mux_isspace(*p))
{
p++;
}
// Leave one space.
//
if (*p)
{
*q++ = ' ';
}
}
}
// Terminate string.
//
*q = '\0';
return buffer;
}
/*
* ---------------------------------------------------------------------------
* * grabto: Return portion of a string up to the indicated character. Also
* * returns a modified pointer to the string ready for another call.
*/
2025-03-24 14:53:29 -06:00
UTF8 *grabto(UTF8 **str, UTF8 targ)
{
UTF8 *savec, *cp;
if (!str || !*str || !**str)
2018-10-03 17:54:51 +00:00
return nullptr;
savec = cp = *str;
while (*cp && *cp != targ)
cp++;
if (*cp)
*cp++ = '\0';
*str = cp;
return savec;
}
2025-03-24 14:53:29 -06:00
int string_compare(const UTF8 *s1, const UTF8 *s2)
{
if (g_space_compress)
{
while (mux_isspace(*s1))
{
s1++;
}
while (mux_isspace(*s2))
{
s2++;
}
while (*s1 && *s2)
{
if (mux_isspace(*s1) && mux_isspace(*s2))
{
// Both are spaces — skip all contiguous spaces.
//
do
{
s1++;
} while (mux_isspace(*s1));
do
{
s2++;
} while (mux_isspace(*s2));
continue;
}
// ASCII fast path: compare via table without the full
// Unicode state machine.
//
if (*s1 < 0x80 && *s2 < 0x80)
{
if (mux_tolower_ascii[*s1] != mux_tolower_ascii[*s2])
{
break;
}
s1++;
s2++;
continue;
}
// Compare code points case-insensitively (Unicode-aware).
//
UTF8 la[4], lb[4];
size_t na, nb;
bool bXorA;
const string_desc *qA = mux_tolower(s1, bXorA);
if (nullptr == qA)
{
na = utf8_FirstByte[static_cast<unsigned char>(*s1)];
if (na >= UTF8_CONTINUE) na = 1;
for (size_t j = 0; j < na; j++) la[j] = s1[j];
}
else
{
na = qA->n_bytes;
if (bXorA)
{
for (size_t j = 0; j < na; j++) la[j] = s1[j] ^ qA->p[j];
}
else
{
for (size_t j = 0; j < na; j++) la[j] = qA->p[j];
}
}
bool bXorB;
const string_desc *qB = mux_tolower(s2, bXorB);
if (nullptr == qB)
{
nb = utf8_FirstByte[static_cast<unsigned char>(*s2)];
if (nb >= UTF8_CONTINUE) nb = 1;
for (size_t j = 0; j < nb; j++) lb[j] = s2[j];
}
else
{
nb = qB->n_bytes;
if (bXorB)
{
for (size_t j = 0; j < nb; j++) lb[j] = s2[j] ^ qB->p[j];
}
else
{
for (size_t j = 0; j < nb; j++) lb[j] = qB->p[j];
}
}
// If lowercased code points differ, we're done.
//
if (na != nb)
{
break;
}
bool bEqual = true;
for (size_t j = 0; j < na; j++)
{
if (la[j] != lb[j])
{
bEqual = false;
break;
}
}
if (!bEqual)
{
break;
}
s1 += na;
s2 += nb;
}
if ( *s1
&& *s2)
{
return 1;
}
if (mux_isspace(*s1))
{
while (mux_isspace(*s1))
{
s1++;
}
return *s1;
}
if (mux_isspace(*s2))
{
while (mux_isspace(*s2))
{
s2++;
}
return *s2;
}
if ( *s1
|| *s2)
{
return 1;
}
return 0;
}
else
{
return mux_stricmp(s1, s2);
}
}
2025-03-24 14:53:29 -06:00
int string_prefix(const UTF8 *string, const UTF8 *prefix)
{
int count = 0;
// ASCII fast path: compare bytes directly via tolower table.
// Most object names and commands are pure ASCII — this avoids
// the full Unicode mux_tolower state machine per character.
//
while (*string && *prefix
&& *string < 0x80 && *prefix < 0x80)
{
if (mux_tolower_ascii[*string] != mux_tolower_ascii[*prefix])
{
return 0;
}
string++;
prefix++;
count++;
}
// Unicode fallback: code-point-at-a-time case-insensitive comparison.
//
while (*string && *prefix)
{
UTF8 la[4], lb[4];
size_t na, nb;
bool bXorA;
const string_desc *qA = mux_tolower(string, bXorA);
if (nullptr == qA)
{
na = utf8_FirstByte[static_cast<unsigned char>(*string)];
if (na >= UTF8_CONTINUE) na = 1;
for (size_t j = 0; j < na; j++) la[j] = string[j];
}
else
{
na = qA->n_bytes;
if (bXorA)
{
for (size_t j = 0; j < na; j++) la[j] = string[j] ^ qA->p[j];
}
else
{
for (size_t j = 0; j < na; j++) la[j] = qA->p[j];
}
}
bool bXorB;
const string_desc *qB = mux_tolower(prefix, bXorB);
if (nullptr == qB)
{
nb = utf8_FirstByte[static_cast<unsigned char>(*prefix)];
if (nb >= UTF8_CONTINUE) nb = 1;
for (size_t j = 0; j < nb; j++) lb[j] = prefix[j];
}
else
{
nb = qB->n_bytes;
if (bXorB)
{
for (size_t j = 0; j < nb; j++) lb[j] = prefix[j] ^ qB->p[j];
}
else
{
for (size_t j = 0; j < nb; j++) lb[j] = qB->p[j];
}
}
if (na != nb)
{
return 0;
}
for (size_t j = 0; j < na; j++)
{
if (la[j] != lb[j])
{
return 0;
}
}
string += na;
prefix += nb;
count += static_cast<int>(na);
}
if (*prefix == '\0')
{
// Matched all of prefix.
//
return count;
}
else
{
return 0;
}
}
/*
* accepts only nonempty matches starting at the beginning of a word
*/
2025-03-24 14:53:29 -06:00
const UTF8 *string_match(const UTF8 *src, const UTF8 *sub)
{
if ((*sub != '\0') && (src))
{
2026-04-04 09:27:28 -06:00
const bool ascii_sub = (*sub < 0x80);
const UTF8 sub0_lower = ascii_sub
? mux_tolower_ascii[*sub]
: 0;
while (*src)
{
2026-04-04 09:27:28 -06:00
if (ascii_sub && *src < 0x80)
{
if (mux_tolower_ascii[*src] != sub0_lower)
{
while (mux_isalnum(*src))
{
src++;
}
while (*src && !mux_isalnum(*src))
{
src++;
}
continue;
}
const UTF8 *s = src;
const UTF8 *p = sub;
while (*s && *p && *s < 0x80 && *p < 0x80)
{
if (mux_tolower_ascii[*s] != mux_tolower_ascii[*p])
{
break;
}
s++;
p++;
}
if (*p == '\0')
{
return src;
}
// If the ASCII prefix matched and either side then went
// non-ASCII, fall back to the full Unicode-aware matcher.
if ((*s || *p) && !(*s < 0x80 && *p < 0x80)
&& string_prefix(src, sub))
{
return src;
}
}
else if (string_prefix(src, sub))
{
return src;
}
// else scan to beginning of next word
//
while (mux_isalnum(*src))
{
src++;
}
while (*src && !mux_isalnum(*src))
{
src++;
}
}
}
return 0;
}
/*
* ---------------------------------------------------------------------------
* * replace_string: Returns an lbuf containing string STRING with all occurances
* * of OLD replaced by NEW. OLD and NEW may be different lengths.
* * (mitch 1 feb 91)
*/
2025-03-24 14:53:29 -06:00
UTF8 *replace_string(const UTF8 *old, const UTF8 *new0, const UTF8 *s)
{
if (!s)
{
2018-10-03 17:54:51 +00:00
return nullptr;
}
size_t olen = strlen(reinterpret_cast<const char *>(old));
UTF8 *result = alloc_lbuf("replace_string");
UTF8 *r = result;
while (*s)
{
// Find next occurrence of the first character of OLD string.
//
const UTF8 *p = reinterpret_cast<const UTF8 *>(strchr(reinterpret_cast<const char *>(s), old[0]));
if ( olen
&& p)
{
// Copy up to the next occurrence of the first char of OLD.
//
size_t n = p - s;
if (n)
{
safe_copy_buf(s, n, result, &r);
s += n;
}
// If we are really at an complete OLD, append NEW to the result
// and bump the input string past the occurrence of OLD.
// Otherwise, copy the character and try matching again.
//
if (!strncmp(reinterpret_cast<const char *>(old), reinterpret_cast<const char *>(s), olen))
{
safe_str(new0, result, &r);
s += olen;
}
else
{
safe_chr(*s, result, &r);
s++;
}
}
else
{
// Finish copying source string. No matches. No further
// work to perform.
//
safe_str(s, result, &r);
break;
}
}
*r = '\0';
return result;
}
2025-03-24 14:53:29 -06:00
bool minmatch(const UTF8 *str, const UTF8 *target, int min)
{
// Code-point-at-a-time case-insensitive comparison (Unicode-aware).
// min tracks bytes matched (callers pass byte-based minlen).
//
while (*str && *target)
{
UTF8 la[4], lb[4];
size_t na, nb;
bool bXorA;
const string_desc *qA = mux_tolower(str, bXorA);
if (nullptr == qA)
{
na = utf8_FirstByte[static_cast<unsigned char>(*str)];
if (na >= UTF8_CONTINUE) na = 1;
for (size_t j = 0; j < na; j++) la[j] = str[j];
}
else
{
na = qA->n_bytes;
if (bXorA)
{
for (size_t j = 0; j < na; j++) la[j] = str[j] ^ qA->p[j];
}
else
{
for (size_t j = 0; j < na; j++) la[j] = qA->p[j];
}
}
bool bXorB;
const string_desc *qB = mux_tolower(target, bXorB);
if (nullptr == qB)
{
nb = utf8_FirstByte[static_cast<unsigned char>(*target)];
if (nb >= UTF8_CONTINUE) nb = 1;
for (size_t j = 0; j < nb; j++) lb[j] = target[j];
}
else
{
nb = qB->n_bytes;
if (bXorB)
{
for (size_t j = 0; j < nb; j++) lb[j] = target[j] ^ qB->p[j];
}
else
{
for (size_t j = 0; j < nb; j++) lb[j] = qB->p[j];
}
}
if (na != nb)
{
return false;
}
for (size_t j = 0; j < na; j++)
{
if (la[j] != lb[j])
{
return false;
}
}
str += na;
target += nb;
min -= static_cast<int>(na);
}
if (*str)
{
return false;
}
if (!*target)
{
return true;
}
return (min <= 0);
}
// --------------------------------------------------------------------------
// StringCloneLen: allocate memory and copy string
//
2025-03-24 14:53:29 -06:00
UTF8 *StringCloneLen(const UTF8 *str, size_t nStr)
{
UTF8 *buff = reinterpret_cast<UTF8 *>(MEMALLOC(nStr+1));
if (buff)
{
memcpy(buff, str, nStr);
buff[nStr] = '\0';
}
else
{
OutOfMemory(reinterpret_cast<const UTF8 *>(__FILE__), __LINE__);
}
return buff;
}
// --------------------------------------------------------------------------
// StringClone: allocate memory and copy string
//
2025-03-24 14:53:29 -06:00
UTF8 *StringClone(const UTF8 *str)
{
return StringCloneLen(str, strlen(reinterpret_cast<const char *>(str)));
}
/* ---------------------------------------------------------------------------
* safe_copy_str - Copy buffers, watching for overflows.
*/
2025-03-24 14:53:29 -06:00
void safe_copy_str(const UTF8 *src, UTF8 *buff, UTF8 **bufp, size_t nSizeOfBuffer)
{
2018-10-03 17:54:51 +00:00
if (src == nullptr) return;
UTF8 *tp = *bufp;
UTF8 *maxtp = buff + nSizeOfBuffer;
while (tp < maxtp && *src)
{
*tp++ = *src++;
}
*bufp = buff + TrimPartialSequence(tp - buff, buff);
}
2025-03-24 14:53:29 -06:00
void safe_copy_str_lbuf(const UTF8 *src, UTF8 *buff, UTF8 **bufp)
{
2018-10-03 17:54:51 +00:00
if (src == nullptr)
{
return;
}
UTF8 *tp = *bufp;
UTF8 *maxtp = buff + LBUF_SIZE - 1;
while (tp < maxtp && *src)
{
*tp++ = *src++;
}
*bufp = buff + TrimPartialSequence(tp - buff, buff);
}
2025-03-24 14:53:29 -06:00
size_t safe_copy_buf(const UTF8 *src, size_t nLen, UTF8 *buff, UTF8 **bufc)
{
fix(lib): safe_copy_buf must not pass a null pointer to memcpy (#1458) UBSan reports "null pointer passed as argument 2" at stringutil.cpp:4348. Passing null to memcpy is UB even when the length is zero -- the standard requires valid pointers regardless (C17 7.24.1p2). The caller was not obvious from the report, so I instrumented safe_copy_buf on Windows to capture a symbolized stack whenever src was null and ran the smoke suite. It fires 5 times, always with nLen == 0, always from the same place: translate_string stringutil.cpp:3643 eval_ecall jit_compiler.cpp:4456 dbt_run dbt.cpp:641 run_cached_program jit_compiler.cpp:2854 jit_eval jit_compiler.cpp:5190 mux_exec ast.cpp:2928 The source is a data table, not a computation. MU_EscapeConvert maps 0x0D (CR) to substitute 4, and MU_Substitutes[4] was { 0, nullptr } -- the deliberate "emit nothing" slot. translate_string hands it straight to safe_copy_buf, which hands it to memcpy. So every CR that passes through translate_string is one of these, which is why it reproduces readily under a sanitizer and never misbehaves without one. Fixed at both ends: - MU_Substitutes[4] is now { 0, T("") }. A null in a table of string literals is a trap for anyone else who indexes it; empty says the same thing and cannot be dereferenced. - safe_copy_buf returns early on a null src. Its siblings safe_copy_str, safe_copy_str_lbuf and utf8_safe_chr all already do this, so tolerating null is the established convention among these helpers and safe_copy_buf was the lone outlier -- worth closing across all 63 call sites rather than only the one that trips today. It also covers TrimPartialSequence, which would dereference src outright had a caller passed null with a non-zero length. No behaviour change: both spellings emit zero bytes. Verified on Windows Server 2022, MSVC 14.51, Release x64. Clean rebuild, 0 warnings, 0 errors. Smoke 316 dispatched / 1487 succeeded / 17 failed -- identical to the pre-change baseline, the 17 being the known build-config set (exp3, UNIX_DIGEST, REALITY_LVLS). The behaviour is already pinned by translate_fn.mux TC004, which asserts translate(%r,p) == %r -- %r is CRLF, so that case consumes substitute 4 (CR, emit nothing) and substitute 3 (LF, "%r"). It still passes, so no new test is needed. Confirmed live as well: @decompile of an attribute holding alpha%rbeta%rgamma returns it unchanged. Windows has no UBSan, so this cannot verify the report itself is gone -- that needs the Linux --enable-sanitizers build from #1449. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:45:52 -06:00
// A null src is UB for memcpy even when nLen is zero -- the standard
// requires valid pointers regardless of the length (C17 7.24.1p2), and
// UBSan reports it as "null pointer passed as argument 2" (#1458).
//
// Guarding here rather than only at the one caller that does it: the
// sibling helpers safe_copy_str, safe_copy_str_lbuf and utf8_safe_chr
// all already return early on a null src, so tolerating it is the
// established convention among these functions and safe_copy_buf was
// the one outlier. It also covers the TrimPartialSequence call below,
// which would dereference src outright had a caller passed null with a
// non-zero length.
//
if (nullptr == src)
{
return 0;
}
size_t left = LBUF_SIZE - (*bufc - buff) - 1;
if (left < nLen)
{
nLen = TrimPartialSequence(left, src);
}
fix(stringutil): safe_copy_buf must not hand memcpy a null source (#1458) The last open item on #1458. UBSan on Linux/aarch64 reported stringutil.cpp:4348:11 null pointer passed as argument 2 against the memcpy in safe_copy_buf. Passing null to memcpy is undefined even when the count is zero -- the parameter is declared non-null, so the standard does not care that nothing would be copied, and a compiler is entitled to infer from the call that src cannot be null and delete a later check for it. The other seven findings in that issue are already fixed on master and I verified each: the three svdhash misaligned uint32_t loads now go through mux_read_u32, mux_atoi64 accumulates and negates in uint64_t, timeutil's iHash is uint32_t, and dbt_emit_a64 special-cases rot == 0. This was the only one left. ## It is reached, and by a caller that is not wrong Apple clang does not implement this check -- I confirmed a deliberate memcpy(dst, nullptr, 0) goes unreported here even with -fsanitize=undefined,nonnull-attribute,builtin -O0 -- so the report cannot be reproduced on this box. Reachability can be measured instead, and that is the more useful question anyway. Instrumenting safe_copy_buf across a full Makesmoke + Smoke run: 2,392,544 calls, 4 with src == nullptr, all with nLen == 0 Backtraces put all four in translate_string(), via the JIT ECALL path (mux_exec -> jit_eval -> run_cached_program -> dbt_run -> eval_ecall). The source is static LITERAL_STRING_STRUCT MU_Substitutes[NUM_MU_SUBS] = { ... { 0, nullptr }, // 4 ... }; -- a deliberate "substitute nothing" entry. A (pointer, length) pair with length zero is a perfectly good empty string, so the caller is not at fault and the table should not be changed to carry a dummy T(""). The guard belongs in the helper, where it also covers the other 58 call sites. A null src with a NON-zero count deliberately stays undefined: that would be a caller bug and silencing it here would hide it. ## On the instrumentation Two of my probes were vacuous before one worked, which is worth recording because both failures looked like clean results: * A UBSan probe reported nothing -- but so did its own control, a direct memcpy(dst, nullptr, 0). Without that control the "clean" run would have read as evidence the defect was not real. * A stderr probe printed nothing -- because Smoke deletes netmux.log on success, not because the path was cold. Writing to a fixed file instead produced the counts above. Verified: build clean, smoke 1505 passed / 0 failed / 316 of 316 dispatched, test-format 1170 call sites plus 31744 differential assertions green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:07:15 -06:00
Merge master into the safe_copy_buf null fix; keep the stronger guard #1517 landed the same defect an hour before this branch was opened, so master already carried a fix. git merged both mechanically and the result was clean but WRONG: it kept my `if (0 != nLen)` wrapper alongside this branch's null early-return, and left my comment in place claiming things the merged code no longer does -- that MU_Substitutes[4] is `{ 0, nullptr }` (this branch changes it to `{ 0, T("") }`), and that a null src with a non-zero count "stays undefined on purpose" (the early return handles it). Resolved in favour of this branch's version, which is the better fix: * Null-tolerance is the established convention among these helpers. safe_copy_str, safe_copy_str_lbuf and utf8_safe_chr all return early on a null src; safe_copy_buf was the lone outlier. My length-based guard was the inconsistent one. * The early return also covers TrimPartialSequence, which dereferences p[iStart] whenever n > 0 and sits BEFORE my guard. So "leave null with a non-zero count undefined" was not merely a stylistic choice -- it left a null dereference one call away. * `{ 0, T("") }` removes the null at source. A null in a table of string literals is a hazard for whoever indexes it next, and the change is behaviourally identical: safe_copy_buf(T(""), 0) does what safe_copy_buf(nullptr, 0) did. My `if (0 != nLen)` wrapper is dropped as redundant once src cannot be null -- memcpy with a valid pointer and a zero count is well defined. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 06:10:39 -06:00
memcpy(*bufc, src, nLen);
*bufc += nLen;
return nLen;
}
2025-03-24 14:53:29 -06:00
size_t safe_fill(UTF8 *buff, UTF8 **bufc, UTF8 chFill, size_t nSpaces)
{
// Check for buffer limits.
//
size_t nBufferAvailable = LBUF_SIZE - (*bufc - buff) - 1;
if (nSpaces > nBufferAvailable)
{
nSpaces = nBufferAvailable;
}
// Fill with spaces.
//
memset(*bufc, chFill, nSpaces);
*bufc += nSpaces;
return nSpaces;
}
2025-03-24 14:53:29 -06:00
void utf8_safe_chr(const UTF8 *src, UTF8 *buff, UTF8 **bufc)
{
size_t nLen;
size_t nLeft;
2018-10-03 17:54:51 +00:00
if ( nullptr == src
|| UTF8_CONTINUE <= (nLen = utf8_FirstByte[*src]))
{
return;
}
for (size_t i = 1; i < nLen; i++)
{
if ( '\0' == src[i]
|| UTF8_CONTINUE != utf8_FirstByte[src[i]])
{
return;
}
}
nLeft = LBUF_SIZE - (*bufc - buff) - 1;
if (nLeft < nLen)
{
return;
}
memcpy(*bufc, src, nLen);
*bufc += nLen;
}
UTF8 *ConvertToUTF8
(
UTF32 ch
)
{
static UTF8 buffer[6];
const UTF32 byteMask = 0xBF;
const UTF32 byteMark = 0x80;
if (ch < (UTF32)0x80)
{
// ASCII
//
buffer[1] = '\0';
buffer[0] = static_cast<UTF8>(ch);
}
else if (ch < (UTF32)0x800)
{
buffer[2] = '\0';
buffer[1] = static_cast<char>((ch | byteMark) & byteMask);
ch >>= 6;
buffer[0] = static_cast<char>(0xC0 | ch);
}
else if (ch < (UTF32)0x10000)
{
if ( UNI_SUR_HIGH_START <= ch
&& ch <= UNI_SUR_LOW_END)
{
buffer[0] = '\0';
return buffer;
}
buffer[3] = '\0';
buffer[2] =static_cast<char>((ch | byteMark) & byteMask);
ch >>= 6;
buffer[1] = static_cast<char>((ch | byteMark) & byteMask);
ch >>= 6;
buffer[0] = static_cast<char>(0xE0 | ch);
}
else if (ch <= UNI_MAX_LEGAL_UTF32)
{
buffer[4] = '\0';
buffer[3] = static_cast<char>((ch | byteMark) & byteMask);
ch >>= 6;
buffer[2] = static_cast<char>((ch | byteMark) & byteMask);
ch >>= 6;
buffer[1] = static_cast<char>((ch | byteMark) & byteMask);
ch >>= 6;
buffer[0] = static_cast<char>(0xF0 | ch);
}
return buffer;
}
UTF16 *ConvertToUTF16(UTF32 ch)
{
static UTF16 buffer[3];
if ( ch < UNI_SUR_HIGH_START
|| UNI_SUR_LOW_END < ch)
{
// This is the common case.
//
buffer[0] = (UTF16)ch;
buffer[1] = 0x0000;
return buffer;
}
else if (ch <= UNI_MAX_LEGAL_UTF32)
{
const int halfShift = 10;
const UTF32 halfBase = 0x0010000UL;
const UTF32 halfMask = 0x3FFUL;
ch -= halfBase;
buffer[0] = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
buffer[1] = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
buffer[2] = 0x0000;
}
else
{
buffer[0] = UNI_REPLACEMENT_CHAR;
buffer[1] = 0x0000;
}
return buffer;
}
2025-03-24 14:53:29 -06:00
UTF32 ConvertFromUTF8(const UTF8 *pString)
{
size_t t = utf8_FirstByte[*pString];
if (UTF8_CONTINUE <= t)
{
return UNI_EOF;
}
UTF32 ch;
if (1 == t)
{
// This is the most common case, and the value is always smaller than
// UNI_SUR_HIGH_START.
//
return pString[0];
}
else if (2 == t)
{
if ( '\0' == pString[1]
|| UTF8_CONTINUE != utf8_FirstByte[pString[1]])
{
return UNI_EOF;
}
ch = utf8_decode_raw(pString, 2);
if (!utf8_is_valid_scalar(ch, 2))
{
return UNI_EOF;
}
}
else if (3 == t)
{
if ( '\0' == pString[1]
|| '\0' == pString[2]
|| UTF8_CONTINUE != utf8_FirstByte[pString[1]]
|| UTF8_CONTINUE != utf8_FirstByte[pString[2]])
{
return UNI_EOF;
}
ch = utf8_decode_raw(pString, 3);
if (!utf8_is_valid_scalar(ch, 3))
{
return UNI_EOF;
}
}
else if (4 == t)
{
if ( '\0' == pString[1]
|| '\0' == pString[2]
|| '\0' == pString[3]
|| UTF8_CONTINUE != utf8_FirstByte[pString[1]]
|| UTF8_CONTINUE != utf8_FirstByte[pString[2]]
|| UTF8_CONTINUE != utf8_FirstByte[pString[3]])
{
return UNI_EOF;
}
ch = utf8_decode_raw(pString, 4);
if (!utf8_is_valid_scalar(ch, 4))
{
return UNI_EOF;
}
}
else
{
return UNI_EOF;
}
if (utf8_is_valid_scalar(ch, t))
{
return ch;
}
else
{
return UNI_EOF;
}
}
2025-03-24 14:53:29 -06:00
size_t ConvertFromUTF16(UTF16 *pString, UTF32 &ch)
{
ch = pString[0];
if ( ch < UNI_SUR_HIGH_START
|| UNI_SUR_LOW_END < ch)
{
// This is the most-common case.
//
return 1;
}
2007-09-26 12:04:34 -07:00
else if (ch <= UNI_SUR_HIGH_END)
{
UTF32 ch2 = pString[1];
if ( UNI_SUR_LOW_START <= ch2
&& ch2 <= UNI_SUR_LOW_END)
{
2022-03-11 12:12:59 -07:00
const int halfShift = 10;
const UTF32 halfBase = 0x0010000UL;
ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
+ (ch2 - UNI_SUR_LOW_START)
+ halfBase;
return 2;
}
}
ch = UNI_EOF;
return 0;
}
2025-03-24 14:53:29 -06:00
UTF16 *ConvertFromUTF8ToUTF16(const UTF8 *pString, size_t& length)
{
static UTF16 buffer[2*LBUF_SIZE];
UTF16 *p = buffer;
UTF16 *const pEnd = buffer + sizeof(buffer) / sizeof(buffer[0]);
2022-03-11 14:36:04 -07:00
length = 0;
while ('\0' != *pString)
{
UTF32 ch = ConvertFromUTF8(pString);
if (UNI_EOF == ch)
{
2018-10-03 17:54:51 +00:00
return nullptr;
}
UTF16 *q = ConvertToUTF16(ch);
while (0x0000 != *q)
{
if (p + 1 >= pEnd)
{
return nullptr;
}
*p++ = *q++;
}
size_t nAdvance = utf8_FirstByte[static_cast<unsigned char>(*pString)];
if (nAdvance < 1 || nAdvance >= UTF8_CONTINUE)
{
return nullptr;
}
pString += nAdvance;
}
if (p >= pEnd)
{
return nullptr;
}
*p = '\0';
2022-03-11 14:36:04 -07:00
length = p - buffer;
return buffer;
}
// We want to remove mal-formed ESC sequences completely and convert the
// well-formed ones.
//
2025-03-24 14:53:29 -06:00
UTF8 *ConvertToUTF8(const char *p, size_t *pn)
{
*pn = 0;
static UTF8 aBuffer[LBUF_SIZE];
UTF8 *pBuffer = aBuffer;
while ('\0' != *p)
{
if (ESC_CHAR != *p)
{
const UTF8 *q = latin1_utf8[static_cast<unsigned char>(*p)];
utf8_safe_chr(q, aBuffer, &pBuffer);
p++;
}
else
{
// We have an ANSI sequence.
//
p++;
if ('[' == *p)
{
p++;
const char *q = p;
while (ANSI_TokenTerminatorTable[static_cast<unsigned char>(*q)] == 0)
{
q++;
}
if ('\0' != q[0])
{
// The segment [p,q) should contain a list of semi-color delimited codes.
//
const char *r = p;
while (r != q)
{
while ( r != q
&& ';' != r[0])
{
r++;
}
// The segment [p,r) should contain one code.
//
size_t n = r - p;
2018-10-03 17:54:51 +00:00
const UTF8 *s = nullptr;
switch (n)
{
case 1:
if ('0' == *p)
{
s = aColors[COLOR_INDEX_RESET].pUTF;
}
else if ('1' == *p)
{
s = aColors[COLOR_INDEX_INTENSE].pUTF;
}
else if ('4' == *p)
{
s = aColors[COLOR_INDEX_UNDERLINE].pUTF;
}
else if ('5' == *p)
{
s = aColors[COLOR_INDEX_BLINK].pUTF;
}
else if ('7' == *p)
{
s = aColors[COLOR_INDEX_INVERSE].pUTF;
}
break;
case 2:
if ('3' == *p)
{
unsigned int iCode = COLOR_INDEX_FG + (p[1] - '0');
if ( COLOR_INDEX_FG <= iCode
2010-08-11 18:15:00 -07:00
&& iCode < COLOR_INDEX_FG + 7)
{
s = aColors[iCode].pUTF;
}
}
else if ('4' == *p)
{
unsigned int iCode = COLOR_INDEX_BG + (p[1] - '0');
if ( COLOR_INDEX_BG <= iCode
2010-08-11 18:15:00 -07:00
&& iCode <= COLOR_INDEX_BG + 7)
{
s = aColors[iCode].pUTF;
}
}
break;
}
2018-10-03 17:54:51 +00:00
if (nullptr != s)
{
utf8_safe_chr(s, aBuffer, &pBuffer);
}
while ( r != q
&& ';' == r[0])
{
r++;
}
p = r;
}
// Eat trailing terminator.
//
p = q + 1;
}
else
{
// Skip to end of mal-formed ANSI sequence.
//
p = q;
}
}
}
}
*pBuffer = '\0';
*pn = pBuffer - aBuffer;
return aBuffer;
}
// mux_strncpy: Copies up to specified number of chars from source.
// Note: unlike strncpy(), this null-terminates after copying.
//
2025-03-24 14:53:29 -06:00
void mux_strncpy(UTF8 *dest, const UTF8 *src, size_t length_to_copy)
{
size_t i = 0;
2022-03-12 14:11:54 -07:00
if ( nullptr != src
|| length_to_copy < 0)
{
2022-03-12 14:11:54 -07:00
while ( i < length_to_copy
&& '\0' != src[i])
{
dest[i] = src[i];
i++;
}
}
dest[i] = '\0';
}
2025-03-24 14:53:29 -06:00
bool matches_exit_from_list(const UTF8 *str, const UTF8 *pattern)
{
2008-02-24 09:25:34 -08:00
const UTF8 *s;
while (*pattern)
{
// Compare str against this exit name, code-point-at-a-time
// (Unicode-aware).
//
s = str;
while ( *s
&& *pattern
&& *pattern != EXIT_DELIMITER)
{
UTF8 la[4], lb[4];
size_t na, nb;
bool bXorA;
const string_desc *qA = mux_tolower(s, bXorA);
if (nullptr == qA)
{
na = utf8_FirstByte[static_cast<unsigned char>(*s)];
if (na >= UTF8_CONTINUE) na = 1;
for (size_t j = 0; j < na; j++) la[j] = s[j];
}
else
{
na = qA->n_bytes;
if (bXorA)
{
for (size_t j = 0; j < na; j++) la[j] = s[j] ^ qA->p[j];
}
else
{
for (size_t j = 0; j < na; j++) la[j] = qA->p[j];
}
}
bool bXorB;
const string_desc *qB = mux_tolower(pattern, bXorB);
if (nullptr == qB)
{
nb = utf8_FirstByte[static_cast<unsigned char>(*pattern)];
if (nb >= UTF8_CONTINUE) nb = 1;
for (size_t j = 0; j < nb; j++) lb[j] = pattern[j];
}
else
{
nb = qB->n_bytes;
if (bXorB)
{
for (size_t j = 0; j < nb; j++) lb[j] = pattern[j] ^ qB->p[j];
}
else
{
for (size_t j = 0; j < nb; j++) lb[j] = qB->p[j];
}
}
if (na != nb)
{
break;
}
bool bEqual = true;
for (size_t j = 0; j < na; j++)
{
if (la[j] != lb[j])
{
bEqual = false;
break;
}
}
if (!bEqual)
{
break;
}
s += na;
pattern += nb;
}
// Did we match it all?
//
if (*s == '\0')
{
// Make sure nothing afterwards
//
while (mux_isspace(*pattern))
{
pattern++;
}
// Did we get it?
//
if ( !*pattern
|| (*pattern == EXIT_DELIMITER))
{
return true;
}
}
// We didn't get it, find next string to test
//
while ( *pattern
&& *pattern++ != EXIT_DELIMITER)
{
; // Nothing.
}
while (mux_isspace(*pattern))
{
pattern++;
}
}
return false;
}
2025-03-24 14:53:29 -06:00
void string_token::set_source(UTF8* source_arg)
{
source = source_arg;
}
2025-03-24 14:53:29 -06:00
void string_token::set_control(const UTF8* control_arg)
{
memset(control, 0, sizeof(control));
2018-10-03 17:54:51 +00:00
// The '\0' character is always a control character.
//
control[0] = true;
// Record the user-specified control characters.
//
while (*control_arg)
{
control[static_cast<unsigned char>(*control_arg)] = 1;
control_arg++;
}
}
2025-03-24 14:53:29 -06:00
UTF8* string_token::parse_length(size_t* length)
{
*length = 0;
UTF8 *p = source;
if (!p)
{
2018-10-03 17:54:51 +00:00
return nullptr;
}
// Skip over leading control characters except for the NUL character.
//
while (control[static_cast<unsigned char>(*p)] && *p)
{
p++;
}
UTF8 *pReturn = p;
// Skip over non-control characters.
//
while (control[static_cast<unsigned char>(*p)] == 0)
{
p++;
}
// What is the length of this token?
//
*length = p - pReturn;
// Terminate the token with a NUL.
//
if (p[0])
{
// We found a non-NUL delimiter, so the next call will begin parsing
// on the character after this one.
//
source = p+1;
}
else
{
// We hit the end of the string, so the end of the string is where
// the next call will begin.
//
source = p;
}
// Did we find a token?
//
if (*length > 0)
{
return pReturn;
}
else
{
2018-10-03 17:54:51 +00:00
return nullptr;
}
}
UTF8* string_token::parse()
{
size_t nLen;
UTF8 *p = parse_length(&nLen);
if (p)
{
p[nLen] = '\0';
}
return p;
}
mux_field StripTabsAndTruncate
(
const UTF8 *pString,
UTF8 *pBuffer,
size_t nLength,
size_t nWidth0
)
{
mux_field fldOutput(0, 0);
2018-10-03 17:54:51 +00:00
if ( nullptr == pBuffer
|| nullptr == pString
|| 0 == nLength
|| 0 == nWidth0
|| '\0' == pString[0])
{
2018-10-03 17:54:51 +00:00
if ( nullptr != pBuffer
&& 0 < nLength)
{
pBuffer[0] = '\0';
}
return fldOutput;
}
LBUF_OFFSET nWidth = static_cast<LBUF_OFFSET>(nWidth0);
if (nLength < nWidth)
{
nWidth = static_cast<LBUF_OFFSET>(nLength);
}
mux_cursor curPos(0, 0);
mux_field fldLimit(nLength, nWidth);
mux_field fldTransition(0, 0);
mux_field fldNormal(0, 0);
2018-10-03 17:54:51 +00:00
const UTF8 *pTransition = nullptr, *pNormal = nullptr;
size_t nNormalBytes = 0, nTransition = 0;
ColorState csCurrent = CS_NORMAL, csNext = CS_NORMAL;
while ('\0' != pString[curPos.m_byte])
{
int iCode = mux_color(pString + curPos.m_byte);
size_t nPointBytes = utf8_FirstByte[static_cast<unsigned char>(pString[curPos.m_byte])];
if (nPointBytes >= UTF8_CONTINUE)
{
nPointBytes = UTF8_SIZE1;
}
if (nPointBytes > nLength - curPos.m_byte)
{
nPointBytes = nLength - curPos.m_byte;
}
if (0 == nPointBytes)
{
break;
}
mux_cursor curPoint(static_cast<LBUF_OFFSET>(nPointBytes), 1);
if (COLOR_NOTCOLOR != iCode)
{
csNext = UpdateColorState(csNext, iCode, pString + curPos.m_byte);
}
2018-10-03 17:54:51 +00:00
else if (nullptr == strchr("\r\n\t", pString[curPos.m_byte]))
{
int nPointWidth = ConsoleWidth(pString + curPos.m_byte);
mux_field fldPoint(static_cast<LBUF_OFFSET>(nPointBytes), static_cast<LBUF_OFFSET>(nPointWidth));
if (csCurrent != csNext)
{
pTransition = ColorTransitionBinary(csCurrent, csNext, &nTransition);
pNormal = ColorBinaryNormal(csNext, &nNormalBytes);
fldNormal(nNormalBytes, 0);
}
else
{
nTransition = 0;
}
fldTransition(nTransition, 0);
if (fldOutput + fldTransition + fldPoint + fldNormal <= fldLimit)
{
if (0 < nTransition)
{
memcpy(pBuffer + fldOutput.m_byte, pTransition, nTransition);
csCurrent = csNext;
}
fldOutput += fldTransition;
for (size_t j = 0; j < fldPoint.m_byte; j++)
{
pBuffer[fldOutput.m_byte + j] = pString[curPos.m_byte + j];
}
fldOutput += fldPoint;
}
else
{
break;
}
}
curPos += curPoint;
}
if (csCurrent != csNext)
{
pNormal = ColorBinaryNormal(csCurrent, &nNormalBytes);
fldNormal(nNormalBytes, 0);
}
if ( 0 < nNormalBytes
&& fldOutput + fldNormal <= fldLimit)
{
memcpy(pBuffer + fldOutput.m_byte, pNormal, nNormalBytes);
fldOutput += fldNormal;
}
pBuffer[fldOutput.m_byte] = '\0';
return fldOutput;
}
// TruncateToBuffer()
//
// pString is parsed into alternating runs of text and color. These runs are
// then encoded into the given buffer with proper truncation. Every run of
// text will be of the same color. The color is collapsed into the minimal
// expression necessary to change from the color of the last run of text to
// the color of the next run of text. The initial color state and last color
// state are both CS_NORMAL.
//
// Parsing runs of text: As long as mux_color() returns COLOR_NOTCOLOR, we are
// in a run of text.
//
// Parsing runs of color: As long as mux_color() returns something besides
// COLOR_NOTCOLOR, we are in a run of color code points and use
// UpdateColorState() to merge the color code point into the current color
// state.
//
// It is sometimes not necessary to parse the entire string. Once we truncate
// a run of text, we know that no runs of text after the truncated one will
// fit either.
//
// The initial state is { CS_NORMAL, "" }. In turn, new runs of text,
// { CS(i), TEXT(i) } are encoded. To encode { CS(i+1), TEXT(i+1) } on the
// end of { CS(i), TEXT(i) }, we use ColorTransitionBinary(CS(i), CS(i+1), ...)
// and ColorBinaryNormal(CS(i+1), ...) to determine the two transitions. If
// there isn't enough room remaining in the buffer for these transitions,
// TEXT(i+1), TEXT(i+1) is truncated until there is room. If TEXT(i+1) is
// truncated to zero, neither color transition nor any of TEXT(i+1) is used.
//
size_t TruncateToBuffer
(
const UTF8 *pString,
UTF8 *pBuffer,
size_t nBuffer
)
{
size_t nOutput = 0;
2018-10-03 17:54:51 +00:00
if ( nullptr == pBuffer
|| nullptr == pString
|| 0 == nBuffer
|| '\0' == pString[0])
{
2018-10-03 17:54:51 +00:00
if (nullptr != pBuffer)
{
pBuffer[0] = '\0';
}
return nOutput;
}
size_t nNormal;
const UTF8 *pNormal;
2007-12-15 10:37:58 -08:00
ColorState csLast = CS_NORMAL;
ColorState csCurrent = CS_NORMAL;
bool bText = false;
const UTF8 *p = pString;
bool bTruncated = false;
while ( '\0' != p[0]
&& !bTruncated)
{
// Parse a run of color code points.
//
int iCode;
while ( ( UTF8_SIZE3 == utf8_FirstByte[p[0]]
|| UTF8_SIZE4 == utf8_FirstByte[p[0]])
&& COLOR_NOTCOLOR != (iCode = mux_color(p)))
{
csCurrent = UpdateColorState(csCurrent, iCode, p);
size_t nAdvance = utf8_advance_nul(p);
if (0 == nAdvance)
{
break;
}
p += nAdvance;
}
// Parse a run of text. A run of text is always ended by '\0' and
// sometimes by '\xEF' since all color code points start with '\xEF'.
//
bText = false;
size_t nTextRun = 0;
const UTF8 *pTextRun = p;
for (;;)
{
const UTF8 *pEF, *pF3;
if (nullptr != (pEF = reinterpret_cast<const UTF8 *>(strchr(reinterpret_cast<const char *>(p), '\xEF'))))
{
nTextRun += pEF - p;
p = pEF;
if (COLOR_NOTCOLOR != mux_color(p))
{
break;
}
size_t nAdvance = utf8_advance_nul(p);
nTextRun += nAdvance;
p += nAdvance;
}
else if (nullptr != (pF3 = reinterpret_cast<const UTF8 *>(strchr(reinterpret_cast<const char *>(p), '\xF3'))))
{
nTextRun += pF3 - p;
p = pF3;
if (COLOR_NOTCOLOR != mux_color(p))
{
break;
}
size_t nAdvance = utf8_advance_nul(p);
nTextRun += nAdvance;
p += nAdvance;
}
else
{
size_t n = strlen(reinterpret_cast<const char *>(p));
nTextRun += n;
p += n;
break;
}
}
// We have either reached a color code point or end of the string.
// We have seen { csCurrent, (nTextRun, pTextRun) }. Before we parse
// further, we need to encode this into the destination buffer.
// There is a color transition, then a possibly truncated run of text
// followed by another color transition to CS_NORMAL. We won't lay
// anything down unless there is room for at least one character of
// the text.
//
if (0 < nTextRun)
{
// Calculate the two transitions.
//
size_t nTransition;
const UTF8 *pTransition = ColorTransitionBinary(csLast, csCurrent, &nTransition);
pNormal = ColorBinaryNormal(csCurrent, &nNormal);
size_t nMinChar = utf8_advance_nul(pTextRun);
if (0 == nMinChar)
{
nMinChar = 1;
}
if (nOutput + nTransition + nMinChar + nNormal <= nBuffer)
{
// Lay down the initial color transition.
//
if (0 < nTransition)
{
memcpy(pBuffer + nOutput, pTransition, nTransition);
nOutput += nTransition;
csLast = csCurrent;
}
if ( nBuffer < nOutput + nTextRun + nNormal
&& nOutput + nNormal <= nBuffer)
{
// We need to truncate the text.
//
nTextRun = nBuffer - (nOutput + nNormal);
while ( 0 < nTextRun
&& UTF8_CONTINUE <= utf8_FirstByte[pTextRun[nTextRun]])
{
nTextRun--;
}
bTruncated = true;
}
// Lay down text.
//
bText = true;
memcpy(pBuffer + nOutput, pTextRun, nTextRun);
nOutput += nTextRun;
// We have left room for the transition to CS_NORMAL, but it
// isn't laid down.
//
}
}
}
pNormal = ColorBinaryNormal((bText)?csCurrent:csLast, &nNormal);
if ( 0 < nNormal
&& nOutput + nNormal <= nBuffer)
{
memcpy(pBuffer + nOutput, pNormal, nNormal);
nOutput += nNormal;
}
pBuffer[nOutput] = '\0';
return nOutput;
}
mux_field PadField( UTF8 *pBuffer, size_t nMaxBytes, LBUF_OFFSET nMinWidth,
mux_field fldOutput)
{
2018-10-03 17:54:51 +00:00
if (nullptr == pBuffer)
{
return fldMin;
}
while ( fldOutput.m_byte < nMaxBytes
&& fldOutput.m_column < nMinWidth)
{
pBuffer[fldOutput.m_byte] = static_cast<UTF8>(' ');
fldOutput += fldAscii;
}
if (fldOutput.m_byte <= nMaxBytes)
{
pBuffer[fldOutput.m_byte] = '\0';
}
return fldOutput;
}
void ItemToList_Init(ITL *p, UTF8 *arg_buff, UTF8 **arg_bufc,
UTF8 arg_chPrefix, UTF8 arg_chSep)
{
p->bFirst = true;
p->chPrefix = arg_chPrefix;
p->chSep = arg_chSep;
p->buff = arg_buff;
p->bufc = arg_bufc;
p->nBufferAvailable = LBUF_SIZE - (*arg_bufc - arg_buff) - 1;
}
bool ItemToList_AddInteger(ITL *pContext, int i)
{
UTF8 smbuf[SBUF_SIZE];
UTF8 *p = smbuf;
if ( !pContext->bFirst
&& pContext->chSep)
{
*p++ = pContext->chSep;
}
if (pContext->chPrefix)
{
*p++ = pContext->chPrefix;
}
p += mux_ltoa(i, p);
size_t nLen = p - smbuf;
if ( pContext->nBufferAvailable < nLen
|| sizeof(smbuf) < nLen)
{
// Out of room.
//
return false;
}
if (pContext->bFirst)
{
pContext->bFirst = false;
}
memcpy(*(pContext->bufc), smbuf, nLen);
*(pContext->bufc) += nLen;
pContext->nBufferAvailable -= nLen;
return true;
}
bool ItemToList_AddInteger64(ITL *pContext, int64_t i64)
{
UTF8 smbuf[SBUF_SIZE];
UTF8 *p = smbuf;
if ( !pContext->bFirst
&& pContext->chSep)
{
*p++ = pContext->chSep;
}
if (pContext->chPrefix)
{
*p++ = pContext->chPrefix;
}
p += mux_i64toa(i64, p);
size_t nLen = p - smbuf;
if ( pContext->nBufferAvailable < nLen
|| sizeof(smbuf) < nLen)
{
// Out of room.
//
return false;
}
if (pContext->bFirst)
{
pContext->bFirst = false;
}
memcpy(*(pContext->bufc), smbuf, nLen);
*(pContext->bufc) += nLen;
pContext->nBufferAvailable -= nLen;
return true;
}
bool ItemToList_AddStringLEN(ITL *pContext, size_t nStr, const UTF8 *pStr)
{
size_t nLen = nStr;
if ( !pContext->bFirst
&& pContext->chSep)
{
nLen++;
}
if (pContext->chPrefix)
{
nLen++;
}
if (nLen > pContext->nBufferAvailable)
{
// Out of room.
//
return false;
}
UTF8 *p = *(pContext->bufc);
if (pContext->bFirst)
{
pContext->bFirst = false;
}
else if (pContext->chSep)
{
*p++ = pContext->chSep;
}
if (pContext->chPrefix)
{
*p++ = pContext->chPrefix;
}
memcpy(p, pStr, nStr);
*(pContext->bufc) += nLen;
pContext->nBufferAvailable -= nLen;
return true;
}
bool ItemToList_AddString(ITL *pContext, const UTF8 *pStr)
{
size_t nStr = strlen(reinterpret_cast<const char *>(pStr));
return ItemToList_AddStringLEN(pContext, nStr, pStr);
}
void ItemToList_Final(ITL *pContext)
{
**(pContext->bufc) = '\0';
}
// mux_stricmp - Compare two strings ignoring case.
//
2025-03-24 14:53:29 -06:00
int mux_stricmp(const UTF8 *a, const UTF8 *b)
{
// ASCII fast path: compare via tolower table without entering
// the Unicode state machine. Falls through to the full path
// when a non-ASCII byte is encountered.
//
while (*a && *b && *a < 0x80 && *b < 0x80)
{
unsigned char la = mux_tolower_ascii[*a];
unsigned char lb = mux_tolower_ascii[*b];
if (la != lb)
{
return (la < lb) ? -1 : 1;
}
a++;
b++;
}
for (;;)
{
if ('\0' == *a || '\0' == *b)
{
// Compare terminating bytes.
//
if (*a < *b) return -1;
if (*a > *b) return 1;
return 0;
}
// Get lowercased bytes for each code point.
//
UTF8 la[4], lb[4];
size_t na, nb;
bool bXorA;
const string_desc *qA = mux_tolower(a, bXorA);
if (nullptr == qA)
{
na = utf8_FirstByte[static_cast<unsigned char>(*a)];
if (na >= UTF8_CONTINUE) na = 1;
for (size_t j = 0; j < na; j++) la[j] = a[j];
}
else
{
na = qA->n_bytes;
if (bXorA)
{
for (size_t j = 0; j < na; j++) la[j] = a[j] ^ qA->p[j];
}
else
{
for (size_t j = 0; j < na; j++) la[j] = qA->p[j];
}
}
bool bXorB;
const string_desc *qB = mux_tolower(b, bXorB);
if (nullptr == qB)
{
nb = utf8_FirstByte[static_cast<unsigned char>(*b)];
if (nb >= UTF8_CONTINUE) nb = 1;
for (size_t j = 0; j < nb; j++) lb[j] = b[j];
}
else
{
nb = qB->n_bytes;
if (bXorB)
{
for (size_t j = 0; j < nb; j++) lb[j] = b[j] ^ qB->p[j];
}
else
{
for (size_t j = 0; j < nb; j++) lb[j] = qB->p[j];
}
}
// Compare the lowercased code-point bytes.
//
size_t nMin = (na < nb) ? na : nb;
for (size_t j = 0; j < nMin; j++)
{
if (la[j] < lb[j]) return -1;
if (la[j] > lb[j]) return 1;
}
if (na < nb) return -1;
if (na > nb) return 1;
a += na;
b += nb;
}
}
// mux_memicmp - Compare two buffers ignoring case (Unicode-aware).
// Bounded by byte count n from each buffer.
//
2025-03-24 14:53:29 -06:00
int mux_memicmp(const void *p1_arg, const void *p2_arg, size_t n)
{
const UTF8 *p1 = reinterpret_cast<const UTF8 *>(p1_arg);
const UTF8 *p2 = reinterpret_cast<const UTF8 *>(p2_arg);
const UTF8 *p1End = p1 + n;
const UTF8 *p2End = p2 + n;
while (p1 < p1End && p2 < p2End)
{
UTF8 la[4], lb[4];
size_t na, nb;
size_t remA = static_cast<size_t>(p1End - p1);
size_t srcA = utf8_FirstByte[static_cast<unsigned char>(*p1)];
if (srcA >= UTF8_CONTINUE)
{
srcA = UTF8_SIZE1;
}
bool bValidA = (srcA <= remA);
if (bValidA)
{
for (size_t i = 1; i < srcA; i++)
{
if (UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(p1[i])])
{
bValidA = false;
break;
}
}
}
if (!bValidA)
{
srcA = UTF8_SIZE1;
}
UTF8 cpA[5];
for (size_t j = 0; j < srcA; j++)
{
cpA[j] = p1[j];
}
cpA[srcA] = '\0';
bool bXorA;
const string_desc *qA = bValidA ? mux_tolower(cpA, bXorA) : nullptr;
if (nullptr == qA)
{
na = srcA;
for (size_t j = 0; j < na; j++) la[j] = cpA[j];
}
else
{
na = qA->n_bytes;
if (bXorA && na > srcA)
{
na = srcA;
}
if (na > remA)
{
na = remA;
}
if (bXorA)
{
for (size_t j = 0; j < na; j++) la[j] = cpA[j] ^ qA->p[j];
}
else
{
for (size_t j = 0; j < na; j++) la[j] = qA->p[j];
}
}
size_t remB = static_cast<size_t>(p2End - p2);
size_t srcB = utf8_FirstByte[static_cast<unsigned char>(*p2)];
if (srcB >= UTF8_CONTINUE)
{
srcB = UTF8_SIZE1;
}
bool bValidB = (srcB <= remB);
if (bValidB)
{
for (size_t i = 1; i < srcB; i++)
{
if (UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(p2[i])])
{
bValidB = false;
break;
}
}
}
if (!bValidB)
{
srcB = UTF8_SIZE1;
}
UTF8 cpB[5];
for (size_t j = 0; j < srcB; j++)
{
cpB[j] = p2[j];
}
cpB[srcB] = '\0';
bool bXorB;
const string_desc *qB = bValidB ? mux_tolower(cpB, bXorB) : nullptr;
if (nullptr == qB)
{
nb = srcB;
for (size_t j = 0; j < nb; j++) lb[j] = cpB[j];
}
else
{
nb = qB->n_bytes;
if (bXorB && nb > srcB)
{
nb = srcB;
}
if (nb > remB)
{
nb = remB;
}
if (bXorB)
{
for (size_t j = 0; j < nb; j++) lb[j] = cpB[j] ^ qB->p[j];
}
else
{
for (size_t j = 0; j < nb; j++) lb[j] = qB->p[j];
}
}
size_t nMin = (na < nb) ? na : nb;
for (size_t j = 0; j < nMin; j++)
{
if (la[j] < lb[j]) return -1;
if (la[j] > lb[j]) return 1;
}
if (na < nb) return -1;
if (na > nb) return 1;
p1 += na;
p2 += nb;
}
if (p1 < p1End) return 1;
if (p2 < p2End) return -1;
return 0;
}
// mux_strlwr - Convert string to all lower case.
//
2025-03-24 14:53:29 -06:00
UTF8 *mux_strlwr(const UTF8 *a, size_t &n)
{
static UTF8 Buffer[LBUF_SIZE];
n = 0;
while ('\0' != *a)
{
size_t j;
size_t src = utf8_FirstByte[static_cast<unsigned char>(*a)];
if (src >= UTF8_CONTINUE)
{
src = UTF8_SIZE1;
}
else
{
for (j = 1; j < src; j++)
{
if ( '\0' == a[j]
|| UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(a[j])])
{
src = UTF8_SIZE1;
break;
}
}
}
size_t out = src;
bool bXor;
const string_desc *qDesc = mux_tolower(a, bXor);
2018-10-03 17:54:51 +00:00
if (nullptr == qDesc)
{
if (LBUF_SIZE-1 < n + out)
{
break;
}
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j];
}
}
else
{
out = qDesc->n_bytes;
if ( bXor
&& out != src)
{
out = src;
qDesc = nullptr;
bXor = false;
}
if (LBUF_SIZE-1 < n + out)
{
break;
}
if (nullptr == qDesc)
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j];
}
}
else if (bXor)
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j] ^ qDesc->p[j];
}
}
else
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = qDesc->p[j];
}
}
}
n += out;
a += src;
}
Buffer[n] = '\0';
return Buffer;
}
// mux_strupr - Convert string to all upper case.
//
2025-03-24 14:53:29 -06:00
UTF8 *mux_strupr(const UTF8 *a, size_t &n)
{
static UTF8 Buffer[LBUF_SIZE];
n = 0;
while ('\0' != *a)
{
size_t j;
size_t src = utf8_FirstByte[static_cast<unsigned char>(*a)];
if (src >= UTF8_CONTINUE)
{
src = UTF8_SIZE1;
}
else
{
for (j = 1; j < src; j++)
{
if ( '\0' == a[j]
|| UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(a[j])])
{
src = UTF8_SIZE1;
break;
}
}
}
size_t out = src;
bool bXor;
const string_desc *qDesc = mux_toupper(a, bXor);
2018-10-03 17:54:51 +00:00
if (nullptr == qDesc)
{
if (LBUF_SIZE-1 < n + out)
{
break;
}
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j];
}
}
else
{
out = qDesc->n_bytes;
if ( bXor
&& out != src)
{
out = src;
qDesc = nullptr;
bXor = false;
}
if (LBUF_SIZE-1 < n + out)
{
break;
}
if (nullptr == qDesc)
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j];
}
}
else if (bXor)
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j] ^ qDesc->p[j];
}
}
else
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = qDesc->p[j];
}
}
}
n += out;
a += src;
}
Buffer[n] = '\0';
return Buffer;
}
// mux_toupper_first - Uppercase the first UTF-8 code point in-place in a
// buffer. buff points to the first byte to transform, bufc points to the
// current end-of-data pointer, nBufferTotal is the total buffer size.
//
// For XOR transforms (same byte length), overwrites in place.
// For literal transforms (may change byte length), shifts the remaining
// buffer contents via memmove and adjusts *bufc.
//
void mux_toupper_first(UTF8 *buff, UTF8 **bufc, size_t nBufferTotal)
{
if ( nullptr == buff
|| nullptr == bufc
|| nullptr == *bufc
|| nBufferTotal < 1
|| buff >= *bufc
|| '\0' == *buff)
{
return;
}
size_t nOld = utf8_FirstByte[static_cast<unsigned char>(*buff)];
if (nOld >= UTF8_CONTINUE)
{
return;
}
// Validate that we have enough bytes.
//
size_t nUsed = *bufc - buff;
if (nOld > nUsed)
{
return;
}
for (size_t j = 1; j < nOld; j++)
{
if ( j >= nUsed
|| UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(buff[j])])
{
return;
}
}
bool bXor;
const string_desc *qDesc = mux_toupper(buff, bXor);
if (nullptr == qDesc)
{
// No transform for this code point — already uppercase or not a letter.
//
return;
}
size_t nNew = qDesc->n_bytes;
if (bXor)
{
// XOR transform: same byte length, overwrite in place.
//
if ( nNew != nOld
|| nNew > nUsed)
{
return;
}
for (size_t j = 0; j < nNew; j++)
{
buff[j] ^= qDesc->p[j];
}
}
else
{
// Literal transform: byte length may differ.
//
size_t nTail = nUsed - nOld;
// Check that the result fits in the buffer.
//
size_t nBufferEnd = nBufferTotal - 1;
size_t nBuffStart = buff - (*bufc - nUsed); // offset of buff from buffer start
if (nBuffStart + nNew + nTail > nBufferEnd)
{
// Would overflow — truncate by not transforming.
//
return;
}
// Shift the tail to make room (or shrink).
//
if (nNew != nOld)
{
memmove(buff + nNew, buff + nOld, nTail + 1); // +1 for NUL
*bufc += (nNew - nOld);
}
// Write the transformed bytes.
//
for (size_t j = 0; j < nNew; j++)
{
buff[j] = qDesc->p[j];
}
}
}
// mux_foldmatch - alias for matching.
//
2025-03-24 14:53:29 -06:00
UTF8 *mux_foldmatch(const UTF8 *a, size_t &n, bool &fChanged)
{
static UTF8 Buffer[LBUF_SIZE];
n = 0;
fChanged = false;
while ('\0' != *a)
{
size_t j;
size_t src = utf8_FirstByte[static_cast<unsigned char>(*a)];
if (src >= UTF8_CONTINUE)
{
src = UTF8_SIZE1;
}
else
{
for (j = 1; j < src; j++)
{
if ( '\0' == a[j]
|| UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(a[j])])
{
src = UTF8_SIZE1;
break;
}
}
}
size_t out = src;
bool bXor;
const string_desc *qDesc = mux_foldmatch(a, bXor);
2018-10-03 17:54:51 +00:00
if (nullptr == qDesc)
{
if (LBUF_SIZE-1 < n + out)
{
break;
}
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j];
}
}
else
{
out = qDesc->n_bytes;
if ( bXor
&& out != src)
{
out = src;
qDesc = nullptr;
bXor = false;
}
if (LBUF_SIZE-1 < n + out)
{
break;
}
if (nullptr == qDesc)
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j];
}
}
else if (bXor)
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = a[j] ^ qDesc->p[j];
}
}
else
{
for (j = 0; j < out; j++)
{
Buffer[n+j] = qDesc->p[j];
}
}
fChanged = true;
}
n += out;
a += src;
}
Buffer[n] = '\0';
return Buffer;
}
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
// Render a double in printf's %f / %e / %g shapes using David M. Gay's dtoa
// for the digits.
//
// Not snprintf: this keeps float rendering on the same correctly-rounded
// digit generator the rest of the tree already uses (mux_ftoa, fval,
// NearestPretty all go through mux_dtoa), rather than making output depend on
// the host libc. mux_ftoa itself is not usable here -- it is MUX's own float
// rendering and switches to exponent form once the decimal point passes 18,
// which %f never does.
//
// dtoa gives digits and a decimal-point position with trailing zeros
// suppressed, so the padding back out to the requested precision is ours.
// decpt == 9999 signals Inf/NaN.
//
// Returns the length written, or 0 with *pbOverflow set when the result will
// not fit nScratch. The caller treats that as truncation; before %f was
// implemented at all this path was a mux_assert(0), so truncating is strictly
// an improvement on aborting the process.
//
#define MUX_FLOAT_SCRATCH 512
static size_t mux_format_double(UTF8 *pScratch, size_t nScratch, double dval,
UTF8 chConv, int nPrec, bool *pbOverflow)
{
*pbOverflow = false;
bool bUpper = ( 'F' == chConv
|| 'E' == chConv
|| 'G' == chConv);
UTF8 chLower = static_cast<UTF8>(bUpper ? chConv - 'A' + 'a' : chConv);
int decpt = 0;
int bNeg = 0;
UTF8 *rve = nullptr;
UTF8 *pDigits = nullptr;
// %g selects between %e and %f on the exponent, so it has to see the
// significant digits before it can choose.
//
int nSig = 0;
if ('g' == chLower)
{
nSig = (0 == nPrec) ? 1 : nPrec;
pDigits = mux_dtoa(dval, 2, nSig, &decpt, &bNeg, &rve);
}
else if ('e' == chLower)
{
pDigits = mux_dtoa(dval, 2, nPrec + 1, &decpt, &bNeg, &rve);
}
else
{
pDigits = mux_dtoa(dval, 3, nPrec, &decpt, &bNeg, &rve);
}
if (nullptr == pDigits)
{
*pbOverflow = true;
return 0;
}
size_t nDigits = static_cast<size_t>(rve - pDigits);
size_t q = 0;
// Inf and NaN. printf spells these "inf"/"nan", uppercased for the
// uppercase conversions; a sign is carried on infinity only.
//
if (9999 == decpt)
{
const char *pWord = ('n' == pDigits[0] || 'N' == pDigits[0]) ? "nan" : "inf";
if ( bNeg
&& 'i' == pWord[0])
{
pScratch[q++] = '-';
}
for (int i = 0; i < 3; i++)
{
pScratch[q++] = static_cast<UTF8>(bUpper ? pWord[i] - 'a' + 'A' : pWord[i]);
}
pScratch[q] = '\0';
return q;
}
// dtoa returns nothing for zero. Treat it as a single '0' digit sitting
// just left of the point, which is what the assembly below expects.
//
UTF8 chZero = '0';
if (0 == nDigits)
{
pDigits = &chZero;
nDigits = 1;
decpt = 1;
}
// Resolve %g to whichever of %e / %f it renders as. C's rule: use %e
// when the exponent is below -4 or at least the precision.
//
bool bStripTrailing = false;
int nExp = decpt - 1;
if ('g' == chLower)
{
bStripTrailing = true;
if ( nExp < -4
|| nSig <= nExp)
{
chLower = 'e';
nPrec = nSig - 1;
}
else
{
chLower = 'f';
nPrec = nSig - 1 - nExp;
if (nPrec < 0)
{
nPrec = 0;
}
}
}
// Worst case is %f of DBL_MAX: ~309 integer digits, plus the point, plus
// the precision. Bail rather than overrun.
//
size_t nWorst = (('f' == chLower) ? (decpt > 0 ? static_cast<size_t>(decpt) : 1)
: 1)
+ static_cast<size_t>(nPrec) + 16;
if (nScratch <= nWorst)
{
*pbOverflow = true;
return 0;
}
if (bNeg)
{
pScratch[q++] = '-';
}
// %g suppresses trailing zeros, but only inside the fraction: the strip
// must never reach back into the integer digits, or "%.1g" of 0 renders
// as the empty string instead of "0". iDot records where the fraction
// begins; bDot says whether there is one at all.
//
size_t iDot = 0;
bool bDot = false;
if ('e' == chLower)
{
pScratch[q++] = pDigits[0];
if (0 < nPrec)
{
iDot = q;
bDot = true;
pScratch[q++] = '.';
for (int i = 0; i < nPrec; i++)
{
size_t k = static_cast<size_t>(i) + 1;
pScratch[q++] = (k < nDigits) ? pDigits[k] : '0';
}
}
if ( bStripTrailing
&& bDot)
{
while ( iDot + 1 < q
&& '0' == pScratch[q-1]) q--;
if (iDot + 1 == q) q = iDot;
}
pScratch[q++] = static_cast<UTF8>(bUpper ? 'E' : 'e');
int nShow = nExp;
pScratch[q++] = static_cast<UTF8>((nShow < 0) ? '-' : '+');
if (nShow < 0) nShow = -nShow;
if (nShow < 10)
{
pScratch[q++] = '0';
pScratch[q++] = static_cast<UTF8>('0' + nShow);
}
else
{
q += mux_ltoa(nShow, pScratch + q);
}
}
else
{
// Integer part.
//
if (decpt <= 0)
{
pScratch[q++] = '0';
}
else
{
for (int i = 0; i < decpt; i++)
{
size_t k = static_cast<size_t>(i);
pScratch[q++] = (k < nDigits) ? pDigits[k] : '0';
}
}
if (0 < nPrec)
{
iDot = q;
bDot = true;
pScratch[q++] = '.';
for (int i = 0; i < nPrec; i++)
{
int k = decpt + i;
pScratch[q++] = ( 0 <= k
&& static_cast<size_t>(k) < nDigits)
? pDigits[static_cast<size_t>(k)] : '0';
}
}
if ( bStripTrailing
&& bDot)
{
while ( iDot + 1 < q
&& '0' == pScratch[q-1]) q--;
if (iDot + 1 == q) q = iDot;
}
}
pScratch[q] = '\0';
return q;
}
// ---------------------------------------------------------------------------
// POSIX %N$ positional arguments for mux_vsnprintf (#1623).
//
// gettext catalogues reorder multi-conversion msgids with %1$s / %2$d. The
// sequential path below cannot resynchronise a va_list cursor, so positional
// formats are handled by: (1) scanning every conversion for its 1-based index
// and type, (2) pulling arguments 1..max from va_list in index order, (3)
// formatting with those saved values. The sequential path is unchanged when
// the format has no %N$.
//
// Mix of positional and non-positional conversions (other than %%) is rejected
// with the #1429 stop policy.
// ---------------------------------------------------------------------------
#define MUX_PRINTF_MAX_ARGS 32
enum mux_printf_akind
{
MPA_NONE = 0,
MPA_INT,
MPA_LONG,
MPA_I64,
MPA_UINT,
MPA_ULONG,
MPA_U64,
MPA_STR,
MPA_PTR,
MPA_DOUBLE,
MPA_CHAR
};
struct mux_printf_arg
{
mux_printf_akind kind;
union
{
int i;
long l;
int64_t i64;
unsigned int u;
unsigned long ul;
uint64_t u64;
UTF8 *s;
void *p;
double d;
unsigned int c;
} v;
};
// True if pFmt contains at least one %N$ conversion (N >= 1).
//
static bool mux_fmt_has_positional(const UTF8 *pFmt)
{
if (nullptr == pFmt)
{
return false;
}
for (const UTF8 *p = pFmt; '\0' != *p; p++)
{
if ('%' != *p)
{
continue;
}
p++;
if ('\0' == *p)
{
break;
}
if ('%' == *p)
{
continue;
}
// Position is the first field after '%': digits then '$'.
//
if ( *p < '1'
|| *p > '9')
{
continue;
}
while ( *p >= '0'
&& *p <= '9')
{
p++;
}
if ('$' == *p)
{
return true;
}
if ('\0' == *p)
{
break;
}
}
return false;
}
// Map a conversion letter + nLongs to an argument kind.
//
static mux_printf_akind mux_fmt_arg_kind(UTF8 ch, int nLongs)
{
switch (ch)
{
case 'd':
case 'i':
if (0 == nLongs)
{
return MPA_INT;
}
if (1 == nLongs)
{
return MPA_LONG;
}
if (2 == nLongs)
{
return MPA_I64;
}
return MPA_NONE;
case 'u':
case 'o':
case 'x':
case 'X':
if (0 == nLongs)
{
return MPA_UINT;
}
if (1 == nLongs)
{
return MPA_ULONG;
}
if (2 == nLongs)
{
return MPA_U64;
}
return MPA_NONE;
case 's':
return (0 == nLongs) ? MPA_STR : MPA_NONE;
case 'p':
return (0 == nLongs) ? MPA_PTR : MPA_NONE;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
return MPA_DOUBLE;
case 'c':
return MPA_CHAR;
default:
return MPA_NONE;
}
}
// Scan pFmt for every conversion: record kind[pos] (1-based). *pMaxPos is
// the highest index referenced. Returns false on mix of positional and
// sequential, type conflict, or an unparseable conversion.
//
static bool mux_fmt_scan_kinds(
const UTF8 *pFmt, mux_printf_akind *kinds, int *pMaxPos)
{
for (int i = 0; i <= MUX_PRINTF_MAX_ARGS; i++)
{
kinds[i] = MPA_NONE;
}
*pMaxPos = 0;
bool bAnyPos = false;
bool bAnySeq = false;
int nNextSeq = 1;
size_t ncpFmt = 0;
if ( nullptr == pFmt
|| !utf8_strlen(pFmt, ncpFmt))
{
return true;
}
size_t iFmt = 0;
while (0 != ncpFmt)
{
if ('%' != pFmt[iFmt])
{
size_t d = utf8_FirstByte[pFmt[iFmt]];
iFmt += d;
ncpFmt--;
continue;
}
iFmt++;
ncpFmt--;
if (0 == ncpFmt)
{
return false;
}
if ('%' == pFmt[iFmt])
{
iFmt++;
ncpFmt--;
continue;
}
int nPos = 0;
int nLongs = 0;
// Optional N$
//
if ( pFmt[iFmt] >= '1'
&& pFmt[iFmt] <= '9')
{
size_t j = iFmt;
size_t n = 0;
size_t nDig = 0;
while ( nDig < ncpFmt
&& pFmt[j] >= '0'
&& pFmt[j] <= '9')
{
n = 10 * n + static_cast<size_t>(pFmt[j] - '0');
j++;
nDig++;
}
if ( nDig < ncpFmt
&& '$' == pFmt[j]
&& n > 0
&& n <= MUX_PRINTF_MAX_ARGS)
{
nPos = static_cast<int>(n);
iFmt = j + 1;
ncpFmt -= nDig + 1;
bAnyPos = true;
}
}
if (0 == nPos)
{
nPos = nNextSeq++;
bAnySeq = true;
}
if ( bAnyPos
&& bAnySeq)
{
return false;
}
// Skip flags / width / precision / length to the type letter.
//
while (0 != ncpFmt)
{
UTF8 ch = pFmt[iFmt];
if ( 'd' == ch || 'i' == ch || 's' == ch || 'u' == ch
|| 'o' == ch || 'x' == ch || 'X' == ch || 'p' == ch
|| 'f' == ch || 'F' == ch || 'e' == ch || 'E' == ch
|| 'g' == ch || 'G' == ch || 'c' == ch)
{
mux_printf_akind k = mux_fmt_arg_kind(ch, nLongs);
if (MPA_NONE == k)
{
return false;
}
if ( MPA_NONE != kinds[nPos]
&& kinds[nPos] != k)
{
return false;
}
kinds[nPos] = k;
if (nPos > *pMaxPos)
{
*pMaxPos = nPos;
}
iFmt++;
ncpFmt--;
break;
}
else if ('l' == ch)
{
nLongs++;
iFmt++;
ncpFmt--;
}
else if ('z' == ch)
{
nLongs = (sizeof(size_t) == sizeof(unsigned long)) ? 1 : 2;
iFmt++;
ncpFmt--;
}
else if ( ('0' <= ch && ch <= '9')
|| '.' == ch
|| '-' == ch
|| '+' == ch
|| ' ' == ch
|| '#' == ch
|| '*' == ch)
{
// Width, precision, flags — including '*' which we do not
// support as a value but must skip past if present.
//
iFmt++;
ncpFmt--;
}
else
{
return false;
}
}
}
return true;
}
static bool mux_fmt_fill_args(
mux_printf_akind *kinds, int nMaxPos, va_list va, mux_printf_arg *args)
{
for (int i = 1; i <= nMaxPos; i++)
{
args[i].kind = kinds[i];
switch (kinds[i])
{
case MPA_INT:
args[i].v.i = va_arg(va, int);
break;
case MPA_LONG:
args[i].v.l = va_arg(va, long);
break;
case MPA_I64:
args[i].v.i64 = va_arg(va, int64_t);
break;
case MPA_UINT:
args[i].v.u = va_arg(va, unsigned int);
break;
case MPA_ULONG:
args[i].v.ul = va_arg(va, unsigned long);
break;
case MPA_U64:
args[i].v.u64 = va_arg(va, uint64_t);
break;
case MPA_STR:
args[i].v.s = va_arg(va, UTF8 *);
break;
case MPA_PTR:
args[i].v.p = va_arg(va, void *);
break;
case MPA_DOUBLE:
args[i].v.d = va_arg(va, double);
break;
case MPA_CHAR:
args[i].v.c = va_arg(va, unsigned int);
break;
case MPA_NONE:
// Gap in the position list — leave zeroed; a later conversion
// that references it will fail closed.
//
break;
default:
return false;
}
}
return true;
}
// mux_vsnprintf - Is an sprintf-like function that will not overflow
// a buffer of specific size. The size is give by count, and count
// should be chosen to include the '\0' termination.
//
// Returns: A number from 0 to count-1 that is the string length of
// the returned (possibly truncated) buffer.
//
// Supports POSIX %N$ positional conversions (#1623) when the format uses
// them; sequential formats keep the original single-pass path.
//
2025-03-24 14:53:29 -06:00
size_t DCL_CDECL mux_vsnprintf(UTF8 *pBuffer, size_t nBuffer, const UTF8 *pFmt, va_list va)
{
2018-10-03 17:54:51 +00:00
if ( nullptr == pBuffer
2009-01-01 22:43:11 -08:00
|| nBuffer < 1)
{
return 0;
}
size_t nLimit = nBuffer-1;
// Positional mode: materialise args, then format with saved values.
//
mux_printf_arg posArgs[MUX_PRINTF_MAX_ARGS + 1];
bool bPositional = false;
int nMaxPos = 0;
memset(posArgs, 0, sizeof(posArgs));
if ( nullptr != pFmt
&& mux_fmt_has_positional(pFmt))
{
mux_printf_akind kinds[MUX_PRINTF_MAX_ARGS + 1];
if ( !mux_fmt_scan_kinds(pFmt, kinds, &nMaxPos)
|| nMaxPos < 1
|| nMaxPos > MUX_PRINTF_MAX_ARGS
|| !mux_fmt_fill_args(kinds, nMaxPos, va, posArgs))
{
// Unparseable or mixed positional/sequential — #1429 stop:
// emit nothing beyond an empty string rather than walk va_list
// wrong. Callers already tolerate truncation.
//
pBuffer[0] = '\0';
return 0;
}
bPositional = true;
}
2009-01-01 22:43:11 -08:00
// Rather than copy a character at a time, some copies are deferred and performed in a single request.
//
2009-02-07 15:19:06 -08:00
size_t iFmtDeferred = 0;
2009-01-01 22:43:11 -08:00
size_t dDeferred = 0;
2009-01-01 22:43:11 -08:00
size_t iBuffer = 0;
size_t ncpFmt;
size_t iFmt = 0;
int nNextSeq = 1;
2018-10-03 17:54:51 +00:00
if ( nullptr != pFmt
2009-01-01 22:43:11 -08:00
&& utf8_strlen(pFmt, ncpFmt))
{
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
// Octal needs 22 digits for a 64-bit value plus the terminator, one
// more than I64BUF_SIZE allows for decimal. Floating point does not
// use this buffer at all -- see the 'f'/'e'/'g' branch, which formats
// straight into the output.
//
static UTF8 Buff[I64BUF_SIZE + 8];
2009-01-01 22:43:11 -08:00
while (0 != ncpFmt)
{
2009-01-01 22:43:11 -08:00
if ('%' != pFmt[iFmt])
{
// Ordinary character.
//
size_t d = utf8_FirstByte[pFmt[iFmt]];
size_t dProposed = dDeferred + d;
if (nLimit < iBuffer + dProposed)
2009-01-01 22:43:11 -08:00
{
if (0 < dDeferred)
{
// Unravel the deferred copy.
//
memcpy(pBuffer + iBuffer, pFmt + iFmtDeferred, dDeferred);
iBuffer += dDeferred;
dDeferred = 0;
}
goto done;
}
else if (0 == dDeferred)
2009-01-01 22:43:11 -08:00
{
iFmtDeferred = iFmt;
}
dDeferred = dProposed;
2009-01-01 22:43:11 -08:00
iFmt += d;
ncpFmt--;
}
else
{
if (0 < dDeferred)
{
// Unravel the deferred copy.
//
memcpy(pBuffer + iBuffer, pFmt + iFmtDeferred, dDeferred);
iBuffer += dDeferred;
dDeferred = 0;
}
size_t cbBuff;
size_t cpBuff;
2009-02-07 15:19:06 -08:00
size_t nWidth = 0;
size_t nPrecision = 0;
2009-01-01 22:43:11 -08:00
bool bLeft = false;
bool bZeroPadded = false;
bool bWidth = false;
bool bSawPeriod = false;
bool bPrecision = false;
2009-01-01 22:43:11 -08:00
int nLongs = 0;
int nArgPos = 0;
2009-01-01 22:43:11 -08:00
fix(lib): echo an unimplemented conversion instead of aborting (#1429) An unimplemented conversion reached mux_assert(0), and that is an unconditional abort() in the shipping build -- mux_assert has no NDEBUG guard and AssertionFailed calls abort() outright. So an ordinary looking T("%+d") did not produce wrong output, it killed the game. #1416 implemented %i, %o and the floating-point conversions, which shrank the surface considerably. This is the other half: the remaining gaps stop being fatal. Both matter together -- implementing conversions means fewer gaps, and not aborting means the next gap someone finds is a formatting bug rather than an outage. That is the pattern #1382 produced twice already (@list cache and astbench), and both were fixed by rewriting the call site to dodge %f rather than by making the formatter survive. The spec is now echoed literally and formatting continues. Emitting it rather than dropping it leaves the evidence in the output, visible without a debugger. A bounded formatter already truncates when it runs out of room, so callers are written against "imperfect output", not against "no server". Caveat, documented at the site: no va_arg is consumed for an unimplemented spec, so a later conversion in the same format string takes a shifted argument. That is inherent to recovering without fully parsing an unknown spec, and is still strictly better than terminating. Added tests/format coverage for the seven forms named in the issue, plus surrounding text and the %% case so the recovery cannot break what already worked. snprintf is deliberately not the oracle for these -- echoing literally is not what printf does -- so they carry explicit expectations. Verified both directions on Win64. With the fix, 31738 passed / 0 failed. Against a libmux rebuilt without it, the same test binary: EXIT=127 C:\tinymux\mux\lib\stringutil.cpp(6701): Assertion failed. Note that stdout was empty in that run: the abort discards even the thousands of results that had already passed. That is the cost of the old behaviour in miniature. Smoke: 315 dispatched, 1477 succeeded, 17 failed -- the known build-configuration failures on this box (exp3, UNIX_DIGEST, REALITY_LVLS). No assertions or crashes in the log. Scope is mux_vsnprintf only; mux_assert elsewhere in the tree is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:12:36 -06:00
// Where this conversion spec starts, so an unimplemented one
// can be echoed literally rather than killing the process
// (#1429).
//
size_t iFmtSpec = iFmt;
2009-01-01 22:43:11 -08:00
iFmt++;
ncpFmt--;
// Optional POSIX position N$ immediately after '%' (#1623).
//
if ( bPositional
&& 0 != ncpFmt
&& pFmt[iFmt] >= '1'
&& pFmt[iFmt] <= '9')
{
size_t j = iFmt;
size_t n = 0;
size_t nDig = 0;
while ( nDig < ncpFmt
&& pFmt[j] >= '0'
&& pFmt[j] <= '9')
{
n = 10 * n + static_cast<size_t>(pFmt[j] - '0');
j++;
nDig++;
}
if ( nDig < ncpFmt
&& '$' == pFmt[j]
&& n > 0
&& n <= MUX_PRINTF_MAX_ARGS)
{
nArgPos = static_cast<int>(n);
iFmt = j + 1;
ncpFmt -= nDig + 1;
}
}
2009-01-01 22:43:11 -08:00
while (0 != ncpFmt)
{
if ( 'd' == pFmt[iFmt]
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
|| 'i' == pFmt[iFmt]
2009-01-01 22:43:11 -08:00
|| 's' == pFmt[iFmt]
|| 'u' == pFmt[iFmt]
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
|| 'o' == pFmt[iFmt]
2009-01-01 22:43:11 -08:00
|| 'x' == pFmt[iFmt]
|| 'X' == pFmt[iFmt]
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
|| 'p' == pFmt[iFmt]
|| 'f' == pFmt[iFmt]
|| 'F' == pFmt[iFmt]
|| 'e' == pFmt[iFmt]
|| 'E' == pFmt[iFmt]
|| 'g' == pFmt[iFmt]
|| 'G' == pFmt[iFmt])
2009-01-01 22:43:11 -08:00
{
UTF8 *pBuff = Buff;
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
UTF8 FBuff[MUX_FLOAT_SCRATCH];
2009-01-01 22:43:11 -08:00
// Resolve which saved arg (positional) or va_arg
// (sequential) to consume.
//
const mux_printf_arg *pArg = nullptr;
if (bPositional)
{
if (0 == nArgPos)
{
nArgPos = nNextSeq++;
}
if ( nArgPos < 1
|| nArgPos > nMaxPos
|| MPA_NONE == posArgs[nArgPos].kind)
{
goto done;
}
pArg = &posArgs[nArgPos];
}
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
if ( 'd' == pFmt[iFmt]
|| 'i' == pFmt[iFmt])
2009-01-01 22:43:11 -08:00
{
// Obtain and validate argument.
//
if (0 == nLongs)
{
int i = (nullptr != pArg) ? pArg->v.i
: va_arg(va, int);
cbBuff = cpBuff = mux_ltoa(i, Buff);
2009-01-01 22:43:11 -08:00
}
else if (1 == nLongs)
{
long int i = (nullptr != pArg) ? pArg->v.l
: va_arg(va, long int);
cbBuff = cpBuff = mux_ltoa(i, Buff);
2009-01-01 22:43:11 -08:00
}
else if (2 == nLongs)
{
int64_t i = (nullptr != pArg) ? pArg->v.i64
: va_arg(va, int64_t);
cbBuff = cpBuff = mux_i64toa(i, Buff);
2009-01-01 22:43:11 -08:00
}
else
{
goto done;
}
}
else if ('s' == pFmt[iFmt])
{
// Obtain and validate argument.
//
pBuff = (nullptr != pArg) ? pArg->v.s
: va_arg(va, UTF8 *);
if ( !utf8_strlen(pBuff, cpBuff)
2009-01-01 22:43:11 -08:00
|| 0 != nLongs)
{
goto done;
}
cbBuff = strlen(reinterpret_cast<char *>(pBuff));
if ( bPrecision
&& nPrecision < cpBuff)
{
// Need to walk cbBuff back to correspond to changes in cpBuff.
//
while (cpBuff != nPrecision)
{
do
{
cbBuff--;
} while (UTF8_CONTINUE <= utf8_FirstByte[pBuff[cbBuff]]);
cpBuff--;
}
}
2009-01-01 22:43:11 -08:00
}
else if ('p' == pFmt[iFmt])
{
if ( 0 != nLongs
|| bWidth)
{
goto done;
}
// Convert pointer to unsigned integer.
//
union
{
uintptr_t ui;
2009-01-01 22:43:11 -08:00
void *pv;
} u;
u.pv = (nullptr != pArg) ? pArg->v.p
: va_arg(va, void *);
2009-01-01 22:43:11 -08:00
#if SIZEOF_UINT_PTR <= SIZEOF_UNSIGNED_LONG
cbBuff = cpBuff = mux_utox(u.ui, Buff, true);
2009-01-01 22:43:11 -08:00
#elif SIZEOF_UINT_PTR <= SIZEOF_UNSIGNED_LONG_LONG
cbBuff = cpBuff = mux_ui64tox(u.ui, Buff, true);
2009-01-01 22:43:11 -08:00
#else
#error Size of pointer is larger size of largest known integer.
#endif
bWidth = true;
nWidth = 2*sizeof(uintptr_t);
2009-01-01 22:43:11 -08:00
bZeroPadded = true;
}
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
else if ( 'f' == pFmt[iFmt]
|| 'F' == pFmt[iFmt]
|| 'e' == pFmt[iFmt]
|| 'E' == pFmt[iFmt]
|| 'g' == pFmt[iFmt]
|| 'G' == pFmt[iFmt])
{
// Floating point. Before this, %f and friends
// fell through to the mux_assert(0) below and took
// the process down -- a standard C conversion this
// function happened not to implement was a server
// abort with no warning at the call site (#1382,
// and again in @list).
//
// Digits come from mux_dtoa, the same correctly
// rounded generator mux_ftoa/fval/NearestPretty
// use, so float output does not depend on the host
// libc. nLongs is ignored: "%lf" is double in
// printf, and "%Lf" never reaches here because 'L'
// is not parsed as a length modifier.
//
double dval = (nullptr != pArg) ? pArg->v.d
: va_arg(va, double);
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
int nPrec = bSawPeriod ? static_cast<int>(nPrecision) : 6;
bool bOverflow = false;
cbBuff = cpBuff = mux_format_double(FBuff,
sizeof(FBuff), dval, pFmt[iFmt], nPrec,
&bOverflow);
if (bOverflow)
{
goto done;
}
pBuff = FBuff;
}
2009-01-01 22:43:11 -08:00
else
{
bool bHex = ( 'x' == pFmt[iFmt]
|| 'X' == pFmt[iFmt]);
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
bool bOct = ('o' == pFmt[iFmt]);
2009-01-01 22:43:11 -08:00
bool bUpper = ('X' == pFmt[iFmt]);
// Obtain and validate argument.
//
if (0 == nLongs)
{
unsigned int ui = (nullptr != pArg) ? pArg->v.u
: va_arg(va, unsigned int);
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
cbBuff = cpBuff = bOct?mux_utoo(ui, Buff):(bHex?mux_utox(ui, Buff, bUpper):mux_utoa(ui, Buff));
2009-01-01 22:43:11 -08:00
}
else if (1 == nLongs)
{
unsigned long int ui = (nullptr != pArg) ? pArg->v.ul
: va_arg(va, unsigned long int);
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
cbBuff = cpBuff = bOct?mux_utoo(ui, Buff):(bHex?mux_utox(ui, Buff, bUpper):mux_utoa(ui, Buff));
2009-01-01 22:43:11 -08:00
}
else if (2 == nLongs)
{
uint64_t ui = (nullptr != pArg) ? pArg->v.u64
: va_arg(va, uint64_t);
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
cbBuff = cpBuff = bOct?mux_ui64too(ui, Buff):(bHex?mux_ui64tox(ui, Buff, bUpper):mux_ui64toa(ui, Buff));
2009-01-01 22:43:11 -08:00
}
else
{
goto done;
}
}
// Calculate and validate needed size. Numberic and
// string fields are at least the size of their width.
// String fields may have been truncated above by
// precision.
2009-01-01 22:43:11 -08:00
//
// Width is compared with the number of code points.
// Padding is always done with space or zero.
//
size_t nUsed = cbBuff;
size_t nPadding = 0;
if ( bWidth
&& cpBuff < nWidth)
{
nPadding = nWidth - cpBuff;
nUsed += nPadding;
}
2009-01-01 22:43:11 -08:00
if (nLimit < iBuffer + nUsed)
{
goto done;
}
// Apply leading padding if necessary.
//
if ( !bLeft
&& bWidth)
{
feat(format): implement %i, %o and floating point in mux_vsnprintf (#1416) mux_vsnprintf implements printf's conversions by hand, and anything it did not implement fell through to mux_assert(0) -- so a caller reaching for a standard C conversion took the server down. That is #1382 in fun_astbench, and independently the same shape in @list. Both were fixed by rewriting the call site to avoid %f, which leaves the trap in place for the next caller. A sweep of every call site through the four wrappers (tprintf, safe_tprintf_str, mux_sprintf, mux_fprintf) found no third instance today: 986 call sites, 909 with a format, 1749 conversion specs, all supported. But "no third instance today" is not a property anyone can maintain by reading, the restriction is invisible at the call site, and the penalty is the whole process. Better to implement the conversions than to keep forbidding them. Added: %i (alias of %d), %o, and %f %F %e %E %g %G. Floating point takes its digits from mux_dtoa -- the same correctly rounded generator mux_ftoa, fval and NearestPretty already use -- so float output does not depend on the host libc. mux_ftoa itself is not usable here: it is MUX's own float rendering and switches to exponent form once the decimal point passes 18, which %f never does. dtoa suppresses trailing zeros and reports a decimal-point position, so padding back out to the requested precision, the %g e-vs-f selection, and the exponent form are assembled here. Note dtoa owns its buffer and reclaims it on the next call (MULTIPLE_THREADS is not defined), so callers must not free -- matching fval. Octal needed mux_utoo/mux_ui64too, and a wider scratch: 64-bit octal is 22 digits, one more than LONGEST_I64 allows for decimal. Hand-assembled float formatting is exactly the code that looks right and is wrong at the boundaries, so tests/format compares against the platform snprintf over 6 conversions x 9 precisions x 5 widths x 3 flags x ~40 values, plus infinities, NaN, negative zero, ties where round-half-even differs, the integer/fraction boundary, %i, %o, 64-bit octal, and truncation. That oracle immediately earned itself: the first run was 31396 passed / 332 failed, all of them %g of values rendering as the empty string. The strip of trailing zeros ran over the whole buffer instead of the fraction, so it ate the integer digit -- "%.1g" of 0 produced "" rather than "0". Bounded to the fraction, 31728 pass and none fail. Wired into `make test` as test-format. Smoke unchanged at 1488 passed, 314/314 dispatched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:45:04 -06:00
// Any signed conversion: the minus sign has to be
// laid down before zero-padding begins, or
// "%08.2f" of -3.5 renders as "0000-3.50".
//
if ( ( 'd' == pFmt[iFmt]
|| 'i' == pFmt[iFmt]
|| 'f' == pFmt[iFmt]
|| 'F' == pFmt[iFmt]
|| 'e' == pFmt[iFmt]
|| 'E' == pFmt[iFmt]
|| 'g' == pFmt[iFmt]
|| 'G' == pFmt[iFmt])
2009-01-01 22:43:11 -08:00
&& '-' == pBuff[0]
&& 0 < nPadding
2009-01-01 22:43:11 -08:00
&& bZeroPadded)
{
// The leading minus sign must be laid down before zero-padding begins.
//
pBuffer[iBuffer] = '-';
iBuffer++;
2009-01-01 22:43:11 -08:00
pBuff++;
cbBuff--;
cpBuff--;
2009-01-01 22:43:11 -08:00
}
while (0 < nPadding)
2009-01-01 22:43:11 -08:00
{
pBuffer[iBuffer] = bZeroPadded?'0':' ';
iBuffer++;
nPadding--;
2009-01-01 22:43:11 -08:00
}
}
// Apply string.
//
memcpy(pBuffer + iBuffer, pBuff, cbBuff);
iBuffer += cbBuff;
2009-01-01 22:43:11 -08:00
// Apply trailing padding if necessary.
//
if ( bLeft
&& bWidth)
2009-01-01 22:43:11 -08:00
{
while (0 < nPadding)
2009-01-01 22:43:11 -08:00
{
pBuffer[iBuffer] = bZeroPadded?'0':' ';
iBuffer++;
nPadding--;
2009-01-01 22:43:11 -08:00
}
}
iFmt++;
ncpFmt--;
break;
}
else if ('l' == pFmt[iFmt])
{
nLongs++;
iFmt++;
ncpFmt--;
}
else if ('z' == pFmt[iFmt])
{
// size_t/ssize_t length modifier. Map onto the
// existing nLongs tiers by actual width so both LP64
// (size_t == unsigned long) and LLP64 (size_t is
// 64-bit while long is 32-bit) marshal the va_arg
// type the caller actually pushed. Without this,
// "%zu" fell through to mux_assert(0) and aborted.
//
nLongs = (sizeof(size_t) == sizeof(unsigned long)) ? 1 : 2;
iFmt++;
ncpFmt--;
}
2009-01-01 22:43:11 -08:00
else if ( '0' <= pFmt[iFmt]
&& pFmt[iFmt] <= '9')
{
if (!bSawPeriod)
2009-01-01 22:43:11 -08:00
{
if (!bWidth)
2009-01-01 22:43:11 -08:00
{
if ('0' == pFmt[iFmt])
2009-01-01 22:43:11 -08:00
{
if (bZeroPadded)
{
goto done;
}
bZeroPadded = true;
}
else
{
nWidth = pFmt[iFmt] - '0';
bWidth = true;
2009-01-01 22:43:11 -08:00
}
}
else
{
nWidth = 10 * nWidth + pFmt[iFmt] - '0';
2009-01-01 22:43:11 -08:00
}
}
else
{
if (!bPrecision)
{
nPrecision = pFmt[iFmt] - '0';
bPrecision = true;
}
else
{
nPrecision = 10 * nPrecision + pFmt[iFmt] - '0';
}
2009-01-01 22:43:11 -08:00
}
iFmt++;
ncpFmt--;
}
else if ('.' == pFmt[iFmt])
{
bSawPeriod = true;
iFmt++;
ncpFmt--;
}
2009-01-01 22:43:11 -08:00
else if ('-' == pFmt[iFmt])
{
if (bLeft)
{
goto done;
}
bLeft = true;
iFmt++;
ncpFmt--;
}
else if ('c' == pFmt[iFmt])
{
unsigned int ch;
if (bPositional)
{
if (0 == nArgPos)
{
nArgPos = nNextSeq++;
}
if ( nArgPos < 1
|| nArgPos > nMaxPos
|| MPA_CHAR != posArgs[nArgPos].kind)
{
goto done;
}
ch = posArgs[nArgPos].v.c;
}
else
{
ch = va_arg(va, unsigned int);
}
2009-01-01 22:43:11 -08:00
if (nLimit < iBuffer + 1)
{
goto done;
}
2010-06-12 13:38:47 -07:00
pBuffer[iBuffer] = static_cast<UTF8>(ch);
2009-01-01 22:43:11 -08:00
iBuffer++;
iFmt++;
ncpFmt--;
break;
}
else if ('%' == pFmt[iFmt])
{
// "%%"
//
if (nLimit < iBuffer + 1)
{
goto done;
}
pBuffer[iBuffer] = '%';
iBuffer++;
iFmt++;
ncpFmt--;
break;
}
else
{
fix(lib): echo an unimplemented conversion instead of aborting (#1429) An unimplemented conversion reached mux_assert(0), and that is an unconditional abort() in the shipping build -- mux_assert has no NDEBUG guard and AssertionFailed calls abort() outright. So an ordinary looking T("%+d") did not produce wrong output, it killed the game. #1416 implemented %i, %o and the floating-point conversions, which shrank the surface considerably. This is the other half: the remaining gaps stop being fatal. Both matter together -- implementing conversions means fewer gaps, and not aborting means the next gap someone finds is a formatting bug rather than an outage. That is the pattern #1382 produced twice already (@list cache and astbench), and both were fixed by rewriting the call site to dodge %f rather than by making the formatter survive. The spec is now echoed literally and formatting continues. Emitting it rather than dropping it leaves the evidence in the output, visible without a debugger. A bounded formatter already truncates when it runs out of room, so callers are written against "imperfect output", not against "no server". Caveat, documented at the site: no va_arg is consumed for an unimplemented spec, so a later conversion in the same format string takes a shifted argument. That is inherent to recovering without fully parsing an unknown spec, and is still strictly better than terminating. Added tests/format coverage for the seven forms named in the issue, plus surrounding text and the %% case so the recovery cannot break what already worked. snprintf is deliberately not the oracle for these -- echoing literally is not what printf does -- so they carry explicit expectations. Verified both directions on Win64. With the fix, 31738 passed / 0 failed. Against a libmux rebuilt without it, the same test binary: EXIT=127 C:\tinymux\mux\lib\stringutil.cpp(6701): Assertion failed. Note that stdout was empty in that run: the abort discards even the thousands of results that had already passed. That is the cost of the old behaviour in miniature. Smoke: 315 dispatched, 1477 succeeded, 17 failed -- the known build-configuration failures on this box (exp3, UNIX_DIGEST, REALITY_LVLS). No assertions or crashes in the log. Scope is mux_vsnprintf only; mux_assert elsewhere in the tree is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:12:36 -06:00
// An unimplemented conversion. This was mux_assert(0),
// which is a hard abort() in the shipping build --
// mux_assert has no NDEBUG guard -- so an ordinary
// looking T("%+d") took the whole game down. #1382
// produced two wizard-triggerable aborts exactly that
// way (@list cache and astbench), and both were fixed
// at the call site rather than here (#1429).
//
fix(lib): stop formatting at an unimplemented conversion (#1445) #1435 stopped an unimplemented conversion from aborting the process: the spec is echoed literally and formatting continues. Echoing is right; continuing is not. No va_arg is consumed for a spec the formatter cannot parse, so from that point the argument list is misaligned and every later conversion in the same format string reads the wrong argument. Measured on master, Linux x86-64: "%d|%d" 11, 22 -> "11|22" baseline "%s|%+d" "head", 11 -> "head|%+d" safe, nothing follows "%+d|%d" 11, 22 -> "%+d|11" silently the WRONG argument "%+d|%s" 11, "tail" -> SIGSEGV The last one hands an int to the %s path as a UTF8* and dereferences it. That trades a clean abort for a wild pointer, which is not the improvement #1429 was for -- a crash that used to name its own file and line is now an unattributed segfault somewhere else. There is no way to resynchronise without knowing what the unknown spec would have taken, and no way to learn that without implementing it. So recovery now stops at the first spec it cannot parse: the remainder of the format is echoed verbatim and interpretation ends. Conversions BEFORE the bad spec are still honoured -- "%s|%+d" still renders its %s -- so stopping is not the same as discarding the format. Echoing the whole remainder rather than just the offending spec keeps the evidence intact: the spec appears in full, with the text that followed it, while nothing further is interpreted. Bug-catch: with the formatter reverted to master's continue and these tests kept, the test binary SEGFAULTS rather than reporting a failure -- the same unambiguous signal astbench gave before #1385. tests/format 31744 passed, 0 failed smoke 1497/1497 on both routes, 0 crashes, 315/315 dispatched Closes #1445.
2026-07-26 15:59:50 -06:00
// Echo the remainder of the format verbatim and STOP
// interpreting. A bounded formatter already truncates
// when it runs out of room, so callers are written
// against "imperfect output" and not against "no
// server"; emitting the text rather than dropping it
// leaves the evidence visible without a debugger.
fix(lib): echo an unimplemented conversion instead of aborting (#1429) An unimplemented conversion reached mux_assert(0), and that is an unconditional abort() in the shipping build -- mux_assert has no NDEBUG guard and AssertionFailed calls abort() outright. So an ordinary looking T("%+d") did not produce wrong output, it killed the game. #1416 implemented %i, %o and the floating-point conversions, which shrank the surface considerably. This is the other half: the remaining gaps stop being fatal. Both matter together -- implementing conversions means fewer gaps, and not aborting means the next gap someone finds is a formatting bug rather than an outage. That is the pattern #1382 produced twice already (@list cache and astbench), and both were fixed by rewriting the call site to dodge %f rather than by making the formatter survive. The spec is now echoed literally and formatting continues. Emitting it rather than dropping it leaves the evidence in the output, visible without a debugger. A bounded formatter already truncates when it runs out of room, so callers are written against "imperfect output", not against "no server". Caveat, documented at the site: no va_arg is consumed for an unimplemented spec, so a later conversion in the same format string takes a shifted argument. That is inherent to recovering without fully parsing an unknown spec, and is still strictly better than terminating. Added tests/format coverage for the seven forms named in the issue, plus surrounding text and the %% case so the recovery cannot break what already worked. snprintf is deliberately not the oracle for these -- echoing literally is not what printf does -- so they carry explicit expectations. Verified both directions on Win64. With the fix, 31738 passed / 0 failed. Against a libmux rebuilt without it, the same test binary: EXIT=127 C:\tinymux\mux\lib\stringutil.cpp(6701): Assertion failed. Note that stdout was empty in that run: the abort discards even the thousands of results that had already passed. That is the cost of the old behaviour in miniature. Smoke: 315 dispatched, 1477 succeeded, 17 failed -- the known build-configuration failures on this box (exp3, UNIX_DIGEST, REALITY_LVLS). No assertions or crashes in the log. Scope is mux_vsnprintf only; mux_assert elsewhere in the tree is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:12:36 -06:00
//
fix(lib): stop formatting at an unimplemented conversion (#1445) #1435 stopped an unimplemented conversion from aborting the process: the spec is echoed literally and formatting continues. Echoing is right; continuing is not. No va_arg is consumed for a spec the formatter cannot parse, so from that point the argument list is misaligned and every later conversion in the same format string reads the wrong argument. Measured on master, Linux x86-64: "%d|%d" 11, 22 -> "11|22" baseline "%s|%+d" "head", 11 -> "head|%+d" safe, nothing follows "%+d|%d" 11, 22 -> "%+d|11" silently the WRONG argument "%+d|%s" 11, "tail" -> SIGSEGV The last one hands an int to the %s path as a UTF8* and dereferences it. That trades a clean abort for a wild pointer, which is not the improvement #1429 was for -- a crash that used to name its own file and line is now an unattributed segfault somewhere else. There is no way to resynchronise without knowing what the unknown spec would have taken, and no way to learn that without implementing it. So recovery now stops at the first spec it cannot parse: the remainder of the format is echoed verbatim and interpretation ends. Conversions BEFORE the bad spec are still honoured -- "%s|%+d" still renders its %s -- so stopping is not the same as discarding the format. Echoing the whole remainder rather than just the offending spec keeps the evidence intact: the spec appears in full, with the text that followed it, while nothing further is interpreted. Bug-catch: with the formatter reverted to master's continue and these tests kept, the test binary SEGFAULTS rather than reporting a failure -- the same unambiguous signal astbench gave before #1385. tests/format 31744 passed, 0 failed smoke 1497/1497 on both routes, 0 crashes, 315/315 dispatched Closes #1445.
2026-07-26 15:59:50 -06:00
// Stopping is the part that is not optional. No
// va_arg was consumed for this spec, so every LATER
// conversion in the same format string would read the
// wrong argument, and there is no way to resynchronise
// without knowing what the unknown spec would have
// taken. Continuing was measured (#1445):
fix(lib): echo an unimplemented conversion instead of aborting (#1429) An unimplemented conversion reached mux_assert(0), and that is an unconditional abort() in the shipping build -- mux_assert has no NDEBUG guard and AssertionFailed calls abort() outright. So an ordinary looking T("%+d") did not produce wrong output, it killed the game. #1416 implemented %i, %o and the floating-point conversions, which shrank the surface considerably. This is the other half: the remaining gaps stop being fatal. Both matter together -- implementing conversions means fewer gaps, and not aborting means the next gap someone finds is a formatting bug rather than an outage. That is the pattern #1382 produced twice already (@list cache and astbench), and both were fixed by rewriting the call site to dodge %f rather than by making the formatter survive. The spec is now echoed literally and formatting continues. Emitting it rather than dropping it leaves the evidence in the output, visible without a debugger. A bounded formatter already truncates when it runs out of room, so callers are written against "imperfect output", not against "no server". Caveat, documented at the site: no va_arg is consumed for an unimplemented spec, so a later conversion in the same format string takes a shifted argument. That is inherent to recovering without fully parsing an unknown spec, and is still strictly better than terminating. Added tests/format coverage for the seven forms named in the issue, plus surrounding text and the %% case so the recovery cannot break what already worked. snprintf is deliberately not the oracle for these -- echoing literally is not what printf does -- so they carry explicit expectations. Verified both directions on Win64. With the fix, 31738 passed / 0 failed. Against a libmux rebuilt without it, the same test binary: EXIT=127 C:\tinymux\mux\lib\stringutil.cpp(6701): Assertion failed. Note that stdout was empty in that run: the abort discards even the thousands of results that had already passed. That is the cost of the old behaviour in miniature. Smoke: 315 dispatched, 1477 succeeded, 17 failed -- the known build-configuration failures on this box (exp3, UNIX_DIGEST, REALITY_LVLS). No assertions or crashes in the log. Scope is mux_vsnprintf only; mux_assert elsewhere in the tree is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:12:36 -06:00
//
fix(lib): stop formatting at an unimplemented conversion (#1445) #1435 stopped an unimplemented conversion from aborting the process: the spec is echoed literally and formatting continues. Echoing is right; continuing is not. No va_arg is consumed for a spec the formatter cannot parse, so from that point the argument list is misaligned and every later conversion in the same format string reads the wrong argument. Measured on master, Linux x86-64: "%d|%d" 11, 22 -> "11|22" baseline "%s|%+d" "head", 11 -> "head|%+d" safe, nothing follows "%+d|%d" 11, 22 -> "%+d|11" silently the WRONG argument "%+d|%s" 11, "tail" -> SIGSEGV The last one hands an int to the %s path as a UTF8* and dereferences it. That trades a clean abort for a wild pointer, which is not the improvement #1429 was for -- a crash that used to name its own file and line is now an unattributed segfault somewhere else. There is no way to resynchronise without knowing what the unknown spec would have taken, and no way to learn that without implementing it. So recovery now stops at the first spec it cannot parse: the remainder of the format is echoed verbatim and interpretation ends. Conversions BEFORE the bad spec are still honoured -- "%s|%+d" still renders its %s -- so stopping is not the same as discarding the format. Echoing the whole remainder rather than just the offending spec keeps the evidence intact: the spec appears in full, with the text that followed it, while nothing further is interpreted. Bug-catch: with the formatter reverted to master's continue and these tests kept, the test binary SEGFAULTS rather than reporting a failure -- the same unambiguous signal astbench gave before #1385. tests/format 31744 passed, 0 failed smoke 1497/1497 on both routes, 0 crashes, 315/315 dispatched Closes #1445.
2026-07-26 15:59:50 -06:00
// "%+d|%d" 11, 22 -> "%+d|11" wrong arg
// "%+d|%s" 11, "tail" -> SIGSEGV
//
// The second hands an int to the %s path as a UTF8*
// and dereferences it. That trades a clean abort for
// a wild pointer, which is not an improvement -- so
// recovery stops at the first spec it cannot parse.
//
// Echoing the whole remainder rather than just this
// spec keeps the evidence intact: the offending spec
// appears in full, with the text that followed it,
// while nothing further is interpreted.
//
size_t nRest =
strlen(reinterpret_cast<const char *>(pFmt) + iFmtSpec);
if (nLimit < iBuffer + nRest)
fix(lib): echo an unimplemented conversion instead of aborting (#1429) An unimplemented conversion reached mux_assert(0), and that is an unconditional abort() in the shipping build -- mux_assert has no NDEBUG guard and AssertionFailed calls abort() outright. So an ordinary looking T("%+d") did not produce wrong output, it killed the game. #1416 implemented %i, %o and the floating-point conversions, which shrank the surface considerably. This is the other half: the remaining gaps stop being fatal. Both matter together -- implementing conversions means fewer gaps, and not aborting means the next gap someone finds is a formatting bug rather than an outage. That is the pattern #1382 produced twice already (@list cache and astbench), and both were fixed by rewriting the call site to dodge %f rather than by making the formatter survive. The spec is now echoed literally and formatting continues. Emitting it rather than dropping it leaves the evidence in the output, visible without a debugger. A bounded formatter already truncates when it runs out of room, so callers are written against "imperfect output", not against "no server". Caveat, documented at the site: no va_arg is consumed for an unimplemented spec, so a later conversion in the same format string takes a shifted argument. That is inherent to recovering without fully parsing an unknown spec, and is still strictly better than terminating. Added tests/format coverage for the seven forms named in the issue, plus surrounding text and the %% case so the recovery cannot break what already worked. snprintf is deliberately not the oracle for these -- echoing literally is not what printf does -- so they carry explicit expectations. Verified both directions on Win64. With the fix, 31738 passed / 0 failed. Against a libmux rebuilt without it, the same test binary: EXIT=127 C:\tinymux\mux\lib\stringutil.cpp(6701): Assertion failed. Note that stdout was empty in that run: the abort discards even the thousands of results that had already passed. That is the cost of the old behaviour in miniature. Smoke: 315 dispatched, 1477 succeeded, 17 failed -- the known build-configuration failures on this box (exp3, UNIX_DIGEST, REALITY_LVLS). No assertions or crashes in the log. Scope is mux_vsnprintf only; mux_assert elsewhere in the tree is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:12:36 -06:00
{
fix(lib): stop formatting at an unimplemented conversion (#1445) #1435 stopped an unimplemented conversion from aborting the process: the spec is echoed literally and formatting continues. Echoing is right; continuing is not. No va_arg is consumed for a spec the formatter cannot parse, so from that point the argument list is misaligned and every later conversion in the same format string reads the wrong argument. Measured on master, Linux x86-64: "%d|%d" 11, 22 -> "11|22" baseline "%s|%+d" "head", 11 -> "head|%+d" safe, nothing follows "%+d|%d" 11, 22 -> "%+d|11" silently the WRONG argument "%+d|%s" 11, "tail" -> SIGSEGV The last one hands an int to the %s path as a UTF8* and dereferences it. That trades a clean abort for a wild pointer, which is not the improvement #1429 was for -- a crash that used to name its own file and line is now an unattributed segfault somewhere else. There is no way to resynchronise without knowing what the unknown spec would have taken, and no way to learn that without implementing it. So recovery now stops at the first spec it cannot parse: the remainder of the format is echoed verbatim and interpretation ends. Conversions BEFORE the bad spec are still honoured -- "%s|%+d" still renders its %s -- so stopping is not the same as discarding the format. Echoing the whole remainder rather than just the offending spec keeps the evidence intact: the spec appears in full, with the text that followed it, while nothing further is interpreted. Bug-catch: with the formatter reverted to master's continue and these tests kept, the test binary SEGFAULTS rather than reporting a failure -- the same unambiguous signal astbench gave before #1385. tests/format 31744 passed, 0 failed smoke 1497/1497 on both routes, 0 crashes, 315/315 dispatched Closes #1445.
2026-07-26 15:59:50 -06:00
nRest = nLimit - iBuffer;
fix(lib): echo an unimplemented conversion instead of aborting (#1429) An unimplemented conversion reached mux_assert(0), and that is an unconditional abort() in the shipping build -- mux_assert has no NDEBUG guard and AssertionFailed calls abort() outright. So an ordinary looking T("%+d") did not produce wrong output, it killed the game. #1416 implemented %i, %o and the floating-point conversions, which shrank the surface considerably. This is the other half: the remaining gaps stop being fatal. Both matter together -- implementing conversions means fewer gaps, and not aborting means the next gap someone finds is a formatting bug rather than an outage. That is the pattern #1382 produced twice already (@list cache and astbench), and both were fixed by rewriting the call site to dodge %f rather than by making the formatter survive. The spec is now echoed literally and formatting continues. Emitting it rather than dropping it leaves the evidence in the output, visible without a debugger. A bounded formatter already truncates when it runs out of room, so callers are written against "imperfect output", not against "no server". Caveat, documented at the site: no va_arg is consumed for an unimplemented spec, so a later conversion in the same format string takes a shifted argument. That is inherent to recovering without fully parsing an unknown spec, and is still strictly better than terminating. Added tests/format coverage for the seven forms named in the issue, plus surrounding text and the %% case so the recovery cannot break what already worked. snprintf is deliberately not the oracle for these -- echoing literally is not what printf does -- so they carry explicit expectations. Verified both directions on Win64. With the fix, 31738 passed / 0 failed. Against a libmux rebuilt without it, the same test binary: EXIT=127 C:\tinymux\mux\lib\stringutil.cpp(6701): Assertion failed. Note that stdout was empty in that run: the abort discards even the thousands of results that had already passed. That is the cost of the old behaviour in miniature. Smoke: 315 dispatched, 1477 succeeded, 17 failed -- the known build-configuration failures on this box (exp3, UNIX_DIGEST, REALITY_LVLS). No assertions or crashes in the log. Scope is mux_vsnprintf only; mux_assert elsewhere in the tree is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:12:36 -06:00
}
fix(lib): stop formatting at an unimplemented conversion (#1445) #1435 stopped an unimplemented conversion from aborting the process: the spec is echoed literally and formatting continues. Echoing is right; continuing is not. No va_arg is consumed for a spec the formatter cannot parse, so from that point the argument list is misaligned and every later conversion in the same format string reads the wrong argument. Measured on master, Linux x86-64: "%d|%d" 11, 22 -> "11|22" baseline "%s|%+d" "head", 11 -> "head|%+d" safe, nothing follows "%+d|%d" 11, 22 -> "%+d|11" silently the WRONG argument "%+d|%s" 11, "tail" -> SIGSEGV The last one hands an int to the %s path as a UTF8* and dereferences it. That trades a clean abort for a wild pointer, which is not the improvement #1429 was for -- a crash that used to name its own file and line is now an unattributed segfault somewhere else. There is no way to resynchronise without knowing what the unknown spec would have taken, and no way to learn that without implementing it. So recovery now stops at the first spec it cannot parse: the remainder of the format is echoed verbatim and interpretation ends. Conversions BEFORE the bad spec are still honoured -- "%s|%+d" still renders its %s -- so stopping is not the same as discarding the format. Echoing the whole remainder rather than just the offending spec keeps the evidence intact: the spec appears in full, with the text that followed it, while nothing further is interpreted. Bug-catch: with the formatter reverted to master's continue and these tests kept, the test binary SEGFAULTS rather than reporting a failure -- the same unambiguous signal astbench gave before #1385. tests/format 31744 passed, 0 failed smoke 1497/1497 on both routes, 0 crashes, 315/315 dispatched Closes #1445.
2026-07-26 15:59:50 -06:00
memcpy(pBuffer + iBuffer, pFmt + iFmtSpec, nRest);
iBuffer += nRest;
goto done;
2009-01-01 22:43:11 -08:00
}
}
}
}
2009-01-01 22:43:11 -08:00
if (0 < dDeferred)
{
2009-01-01 22:43:11 -08:00
// Unravel the deferred copy.
//
memcpy(pBuffer + iBuffer, pFmt + iFmtDeferred, dDeferred);
iBuffer += dDeferred;
dDeferred = 0;
}
}
2009-01-01 22:43:11 -08:00
done:
pBuffer[iBuffer] = '\0';
return iBuffer;
}
2025-03-24 14:53:29 -06:00
void DCL_CDECL mux_sprintf(UTF8 *buff, size_t count, const UTF8 *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
(void)mux_vsnprintf(buff, count, fmt, ap);
2022-03-12 14:11:41 -07:00
va_end(ap);
}
fix(jit): bound ECALL_ORD's write, and convert jit_compiler off raw printf (#1653) Started as the boring half of #1653 -- jit_compiler.cpp held 26 of the 79 remaining raw printf-family sites -- and the guard found a real bug in the process. ECALL_ORD wrote into guest memory with no bound at all: op += sprintf(op, "%ld", static_cast<long>(ch)); It is fun_ord()'s loop transliterated, but the interpreter writes through safe_chr/safe_ltoa, which stop at the buffer end. Dropping those for raw pointer arithmetic dropped the only thing bounding the write. The check above it reads like it bounds the write and does not: if (in_addr >= ec->memory_size || out_addr >= ec->memory_size - 64) That guarantees 64 bytes of headroom against a loop emitting up to 8 bytes per CODEPOINT, and a grapheme cluster is one cluster but any number of codepoints. Measured on ord() of 'a' followed by N combining acutes, which is one cluster of player-supplied input: N=400 1602 bytes written N=1200 4802 bytes written N=3800 15202 bytes written Linear in the input, no cap. It does not crash only because out_addr happens to sit far enough from the end of guest memory; everything past the result slot is overwritten either way, and that margin is accidental rather than checked. Now bounded by the space that actually exists, stopping cleanly the way the interpreter does when its LBUF fills. AST and JIT agree byte-for-byte at N = 5, 60, 400, 1200 and 3800, so the hazard is gone with no behaviour change. Worth noting how it surfaced: my own `grep "snprintf("` missed it, because it is sprintf. The guard's ban list covers the family, so it did not. The other 25 conversions are mechanical, except that two were also latent: - memcpy(dst, nbuf, len + 1) where len is snprintf's WOULD-write, so a truncation would have overrun a small stack buffer - n += snprintf(..., LBUF_SIZE - n, ...) where an over-long n sends a negative through a size_t parameter; guarded today only by an explicit n < LBUF_SIZE - 256 check that exists because of those semantics Both stop being possible with a did-write return. Which needed one: mux_snprintf, in libmux, returning the length mux_vsnprintf already computes and mux_sprintf discards. mail_mod.cpp grew a private mail_sprintf for exactly this, and copying it here would have made a third -- the same duplication #1667's ADR objects to for append_ljust_field, which is byte-identical across two modules today. Registered in check_formats.py so its call sites are checked like any other wrapper. It is NOT snprintf's return: snprintf answers what it WOULD have written, so `n >= size` is how callers detect truncation. This answers what it DID write. Anything ported across that tests the return against the buffer size has to be re-read rather than renamed -- which is why the conversions above drop the clamps instead of keeping them. Legacy raw printf-family sites: 79 -> 53. make test green; test-lua-jit 1561/0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 08:14:11 -06:00
// As mux_sprintf, but returns the length written -- 0..count-1, the value
// mux_vsnprintf already computes and mux_sprintf throws away.
//
// Callers that append in a loop need it, and until now each grew its own
// varargs wrapper because only the va_list form returned a length. There is
// one in mail_mod.cpp already; the next conversion of a raw snprintf() that
// uses its return value would have made a third, and identical private
// copies do not stay identical (see #1667's ADR on append_ljust_field, which
// is byte-for-byte duplicated across two modules today).
//
// Note this is NOT snprintf's return: snprintf answers how much it WOULD
// have written, so `n >= size` is how callers detect truncation. This
// answers how much it DID write, so truncation shows as a short result
// rather than an over-long one. Anything ported from snprintf that tests
// the return against the buffer size has to be re-read, not just renamed.
//
size_t DCL_CDECL mux_snprintf(UTF8 *buff, size_t count, const UTF8 *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
size_t n = mux_vsnprintf(buff, count, fmt, ap);
va_end(ap);
return n;
}
2025-03-24 14:53:29 -06:00
void DCL_CDECL mux_fprintf(FILE *fp, const UTF8 *fmt, ...)
2009-01-02 09:16:25 -08:00
{
2018-10-03 17:54:51 +00:00
if (nullptr != fp)
2009-01-02 09:16:25 -08:00
{
UTF8 Buffer[MBUF_SIZE];
va_list ap;
va_start(ap, fmt);
size_t nBuffer = mux_vsnprintf(Buffer, sizeof(Buffer), fmt, ap);
va_end(ap);
fwrite(Buffer, 1, nBuffer, fp);
}
}
// This function acts like fgets except that any data on the end of the
// line past the buffer size is truncated instead of being returned on
// the next call.
//
size_t GetLineTrunc(UTF8 *Buffer, size_t nBuffer, FILE *fp)
{
size_t lenBuffer = 0;
if (fgets(reinterpret_cast<char *>(Buffer), static_cast<int>(nBuffer), fp))
{
lenBuffer = strlen(reinterpret_cast<char *>(Buffer));
}
if (lenBuffer <= 0)
{
memcpy(Buffer, "\n", 2);
return 1;
}
if (Buffer[lenBuffer-1] != '\n')
{
// The line was too long for the buffer. Continue reading until the
// end of the line.
//
UTF8 TruncBuffer[SBUF_SIZE];
size_t lenTruncBuffer;
do
{
if (!fgets(reinterpret_cast<char *>(TruncBuffer), sizeof(TruncBuffer), fp))
{
break;
}
lenTruncBuffer = strlen(reinterpret_cast<char *>(TruncBuffer));
}
while (TruncBuffer[lenTruncBuffer-1] != '\n');
}
return lenBuffer;
}
// Method: Boyer-Moore-Horspool
//
// This method is a simplification of the Boyer-Moore String Searching
// Algorithm, but a useful one. It does not require as much temporary
// storage, and the setup costs are not as high as the full Boyer-Moore.
//
// If we were searching megabytes of data instead of 8KB at most, then
// the full Boyer-Moore would make more sense.
//
#define BMH_LARGE 32767
void BMH_Prepare(BMH_State *bmhs, size_t nPat, const UTF8 *pPat)
{
if (nPat <= 0)
{
return;
}
size_t k;
for (k = 0; k < 256; k++)
{
bmhs->m_d[k] = nPat;
}
UTF8 chLastPat = pPat[nPat-1];
bmhs->m_skip2 = nPat;
for (k = 0; k < nPat - 1; k++)
{
bmhs->m_d[static_cast<unsigned char>(pPat[k])] = nPat - k - 1;
if (pPat[k] == chLastPat)
{
bmhs->m_skip2 = nPat - k - 1;
}
}
bmhs->m_d[static_cast<unsigned char>(chLastPat)] = BMH_LARGE;
}
bool BMH_Execute(BMH_State *bmhs, size_t *pnMatched, size_t nPat, const UTF8 *pPat, size_t nSrc, const UTF8 *pSrc)
{
if (nPat <= 0)
{
return false;
}
for (size_t i = nPat-1; i < nSrc; i += bmhs->m_skip2)
{
while ((i += bmhs->m_d[static_cast<unsigned char>(pSrc[i])]) < nSrc)
{
; // Nothing.
}
if (i < BMH_LARGE)
{
break;
}
i -= BMH_LARGE;
int j = static_cast<int>(nPat - 1);
const UTF8 *s = pSrc + (i - j);
while (--j >= 0 && s[j] == pPat[j])
{
; // Nothing.
}
if (j < 0)
{
*pnMatched = s-pSrc;
return true;
}
}
return false;
}
bool BMH_StringSearch(size_t *pnMatched, size_t nPat, const UTF8 *pPat, size_t nSrc, const UTF8 *pSrc)
{
BMH_State bmhs;
BMH_Prepare(&bmhs, nPat, pPat);
return BMH_Execute(&bmhs, pnMatched, nPat, pPat, nSrc, pSrc);
}
// BMH_PrepareI - Pre-lowercase the pattern with mux_strlwr() (Unicode-aware)
// and prepare the case-sensitive BMH state on the lowered pattern.
//
// Note: For XOR transforms (the vast majority), byte length is preserved so
// offsets map directly. For the ~24 rare literal transforms that change byte
// count, the offset in the lowered source may differ from the original.
// Callers (grepi) use the offset to extract from the original text, so the
// result is correct when byte length is preserved.
//
void BMH_PrepareI(BMH_State *bmhs, size_t nPat, const UTF8 *pPat)
{
size_t nLower;
UTF8 *pLower = mux_strlwr(pPat, nLower);
// Use a persistent buffer since BMH_Execute needs the pattern later.
//
static UTF8 LoweredPattern[LBUF_SIZE];
if (nLower >= LBUF_SIZE)
{
nLower = LBUF_SIZE - 1;
}
memcpy(LoweredPattern, pLower, nLower);
LoweredPattern[nLower] = '\0';
BMH_Prepare(bmhs, nLower, LoweredPattern);
}
bool BMH_ExecuteI(BMH_State *bmhs, size_t *pnMatched, size_t nPat, const UTF8 *pPat, size_t nSrc, const UTF8 *pSrc)
{
// Lowercase both pattern and source, then delegate to case-sensitive.
//
size_t nLowerPat;
UTF8 *pLowerPat = mux_strlwr(pPat, nLowerPat);
// mux_strlwr uses a static buffer, so copy the lowered pattern first.
//
LBuf LowPat = LBuf_Src("BMH_ExecuteI");
if (nLowerPat >= LBUF_SIZE) nLowerPat = LBUF_SIZE - 1;
memcpy(LowPat, pLowerPat, nLowerPat);
LowPat[nLowerPat] = '\0';
size_t nLowerSrc;
UTF8 *pLowerSrc = mux_strlwr(pSrc, nLowerSrc);
// Re-prepare BMH state with lowered pattern since the state from
// BMH_PrepareI may have been invalidated.
//
BMH_State bmhsLocal;
BMH_Prepare(&bmhsLocal, nLowerPat, LowPat);
return BMH_Execute(&bmhsLocal, pnMatched, nLowerPat, LowPat, nLowerSrc, pLowerSrc);
}
bool BMH_StringSearchI(size_t *pnMatched, size_t nPat, const UTF8 *pPat, size_t nSrc, const UTF8 *pSrc)
{
// Lowercase pattern and source, then use case-sensitive search.
//
size_t nLowerPat;
UTF8 *pLowerPat = mux_strlwr(pPat, nLowerPat);
LBuf LowPat = LBuf_Src("BMH_StringSearchI");
if (nLowerPat >= LBUF_SIZE) nLowerPat = LBUF_SIZE - 1;
memcpy(LowPat, pLowerPat, nLowerPat);
LowPat[nLowerPat] = '\0';
size_t nLowerSrc;
UTF8 *pLowerSrc = mux_strlwr(pSrc, nLowerSrc);
return BMH_StringSearch(pnMatched, nLowerPat, LowPat, nLowerSrc, pLowerSrc);
}
// find_pattern_delimiter - Find the first ':' in a $-command or ^-listen
// pattern that is not part of a '::' escape. A '::' pair represents a literal
// ':' in the pattern (useful for regexp non-capturing groups, etc.). Returns
// a pointer to the delimiter ':', or nullptr if none is found.
//
UTF8 *find_pattern_delimiter(UTF8 *str)
{
UTF8 *p = str;
while (*p)
{
if (':' == *p)
{
if (':' == p[1])
{
p += 2;
continue;
}
return p;
}
p++;
}
return nullptr;
}
// unescape_pattern_colons - Collapse '::' sequences to ':' in-place.
// Called on the pattern portion of a $-command or ^-listen after the
// delimiter has been found and the string split.
//
void unescape_pattern_colons(UTF8 *str)
{
UTF8 *r = str;
UTF8 *w = str;
while (*r)
{
if (':' == r[0] && ':' == r[1])
{
*w++ = ':';
r += 2;
continue;
}
*w++ = *r++;
}
*w = '\0';
}
// strip_fancy_quotes - Replace Unicode smart/curly quotes with ASCII equivalents
// in-place. The string can only shrink (3-byte UTF-8 sequences become 1 byte),
// so no additional buffer is needed.
//
// U+201C / U+201D (left/right double quotation mark) -> "
// U+2018 / U+2019 (left/right single quotation mark) -> '
//
void strip_fancy_quotes(UTF8 *str)
{
UTF8 *r = str;
UTF8 *w = str;
while (*r)
{
if ( 0xE2 == r[0]
&& 0x80 == r[1])
{
if (0x9C == r[2] || 0x9D == r[2])
{
*w++ = '"';
r += 3;
continue;
}
else if (0x98 == r[2] || 0x99 == r[2])
{
*w++ = '\'';
r += 3;
continue;
}
}
*w++ = *r++;
}
*w = '\0';
}
bool mux_fopen(FILE **pFile, const UTF8 *filename, const UTF8 *mode)
{
if (pFile)
{
*pFile = nullptr;
if ( nullptr != filename
&& nullptr != mode)
{
#if defined(WINDOWS_FILES) && !defined(__INTEL_COMPILER) && (_MSC_VER >= 1400)
// 1400 is Visual C++ 2005
//
return (fopen_s(pFile, reinterpret_cast<const char *>(filename), reinterpret_cast<const char *>(mode)) == 0);
#else
*pFile = fopen(reinterpret_cast<const char *>(filename), reinterpret_cast<const char *>(mode));
if (nullptr != *pFile)
{
return true;
}
#endif // WINDOWS_FILES
}
}
return false;
}
void mux_fclose(FILE *fp)
{
if (nullptr != fp)
{
fclose(fp);
}
}
bool mux_open(int *pfh, const UTF8 *filename, int oflag)
{
if (nullptr != pfh)
{
*pfh = MUX_OPEN_INVALID_HANDLE_VALUE;
if (nullptr != filename)
{
#if defined(WINDOWS_FILES) && !defined(__INTEL_COMPILER) && (_MSC_VER >= 1400)
// 1400 is Visual C++ 2005
//
return (_sopen_s(pfh, reinterpret_cast<const char *>(filename), oflag, _SH_DENYNO, _S_IREAD|_S_IWRITE) == 0);
#elif defined(WINDOWS_FILES)
*pfh = _open(reinterpret_cast<const char *>(filename), oflag, _S_IREAD|_S_IWRITE);
return (0 <= *pfh);
#else
*pfh = open(reinterpret_cast<const char *>(filename), oflag, 0600);
return (0 <= *pfh);
#endif
}
}
return false;
}
const UTF8 *mux_strerror(int errnum)
{
#if defined(WINDOWS_FILES) && !defined(__INTEL_COMPILER) && (_MSC_VER >= 1400)
// 1400 is Visual C++ 2005
//
static UTF8 buffer[80];
strerror_s(reinterpret_cast<char *>(buffer), sizeof(buffer), errnum);
return buffer;
#else
return reinterpret_cast<UTF8 *>(strerror(errnum));
#endif
}
size_t LeftJustifyString(UTF8 *field, size_t nWidth, const UTF8 *value)
{
size_t n = strlen(reinterpret_cast<const char *>(value));
if (n > nWidth)
{
n = nWidth;
}
memcpy(field, value, n);
memset(field+n, ' ', nWidth-n);
return nWidth;
}
size_t RightJustifyNumber(UTF8 *field, size_t nWidth, int64_t value, UTF8 chFill)
{
UTF8 buffer[I64BUF_SIZE];
size_t nReturn = 0;
if (nWidth < sizeof(buffer))
{
size_t n = mux_i64toa(value, buffer);
if (n < sizeof(buffer))
{
nReturn = n;
if (n < nWidth)
{
memset(field, chFill, nWidth-n);
field += nWidth-n;
nReturn = nWidth;
}
memcpy(field, buffer, n);
}
}
return nReturn;
}
// ConvertCRLFtoSpace — replace CRLF sequences with spaces.
// Returns a pointer to a static buffer.
//
UTF8 *ConvertCRLFtoSpace(const UTF8 *pString)
{
static UTF8 buf[LBUF_SIZE];
UTF8 *bp = buf;
// Skip any leading CRLF.
//
while ( '\r' == *pString
|| '\n' == *pString)
{
pString++;
}
bool bFirst = true;
while (*pString)
{
if (!bFirst)
{
safe_chr(' ', buf, &bp);
}
else
{
bFirst = false;
}
while ( *pString
&& '\r' != *pString
&& '\n' != *pString)
{
safe_chr(*pString, buf, &bp);
pString++;
}
// Skip any CRLF.
//
while ( '\r' == *pString
|| '\n' == *pString)
{
pString++;
}
}
*bp = '\0';
return buf;
}