#include "cli.h"
static int is_printable_utf8(const uint8_t *data, size_t len)
{
for (size_t i = 0; i < len; i++) {
uint8_t c = data[i];
if (c >= 0x80) {
int extra;
if ((c & 0xE0) == 0xC0) extra = 1;
else if ((c & 0xF0) == 0xE0) extra = 2;
else if ((c & 0xF8) == 0xF0) extra = 3;
else return 0;
for (int j = 0; j < extra; j++) {
if (++i >= len) return 0;
if ((data[i] & 0xC0) != 0x80) return 0;
}
} else if (c < 0x20 && c != '\t' && c != '\n' && c != '\r') {
return 0;
}
}
return 1;
}
typedef struct {
int indent;
int depth;
} PrintCtx;
static void emit_indent(PrintCtx *ctx)
{
if (ctx->indent == 0) return;
printf("\n");
for (int i = 0; i < ctx->depth * ctx->indent; i++)
putchar(' ');
}
static void emit_newline_or_space(PrintCtx *ctx)
{
if (ctx->indent == 0) printf(" ");
else emit_indent(ctx);
}
static void repr_literal_bytes(const uint8_t *data, size_t len)
{
if (!data || len == 0) return;
fwrite(data, 1, len, stdout);
}
static const struct { uint8_t fg; uint8_t bg; } custom_colors[42] = {
{9,8},{10,8},{11,8},{12,8},{13,8},{14,8},{15,8},
{0,9},{10,9},{11,9},{12,9},{14,9},{15,9},
{0,10},{9,10},{12,10},{13,10},
{0,11},{9,11},{12,11},{13,11},
{0,12},{9,12},{10,12},{11,12},{13,12},{14,12},{15,12},
{0,13},{10,13},{11,13},{12,13},{14,13},{15,13},
{0,14},{9,14},{12,14},{13,14},
{0,15},{9,15},{12,15},{13,15},
};
static const uint8_t color_order[42] = {
14,23,34, 1,12,21,32,41,10,19,30,39, 8,17,28,37, 6,15,
26,35, 4,13,24,33, 2,11,22,31, 0, 9,20,29,40, 7,18,27,
38, 5,16,25,36, 3,
};
static void custom_color(uint8_t code, uint8_t *fg, uint8_t *bg)
{
int idx = color_order[(code - DTOB_CUSTOM_MIN) % 42];
*fg = custom_colors[idx].fg;
*bg = custom_colors[idx].bg;
}
static void repr_value_typed(const DtobValue *v, PrintCtx *ctx,
const DtobTypesHeader *types)
{
if (!v) { printf("null"); return; }
switch ((int)v->type) {
case JSON_STRING:
repr_literal_bytes(v->data, v->data_len);
break;
case DTOB_INT: {
int is_negative = (v->data_len > 0 && (v->data[0] & 0x80));
if (is_negative) {
int64_t val = -1;
for (size_t i = 0; i < v->data_len; i++)
val = (val << 8) | v->data[i];
printf("%ld", (long)val);
} else {
uint64_t val = 0;
for (size_t i = 0; i < v->data_len; i++)
val = (val << 8) | v->data[i];
printf("%llu", (unsigned long long)val);
}
break;
}
case DTOB_FLOAT: {
double d;
if (v->data_len == 4) {
float f;
memcpy(&f, v->data, 4);
d = (double)f;
} else {
memcpy(&d, v->data, 8);
}
printf("%g", d);
break;
}
case DTOB_RAW:
if (v->data_len > 0 && is_printable_utf8(v->data, v->data_len)) {
repr_literal_bytes(v->data, v->data_len);
} else {
printf("\033[7m BINARY %zu BYTES \033[0m", v->data_len);
}
break;
case JSON_TRUE: printf("true"); break;
case JSON_FALSE: printf("false"); break;
case JSON_NULL: printf("null"); break;
case DTOB_ARRAY:
putchar('[');
if (v->num_elements == 0) {
putchar(']');
} else {
ctx->depth++;
for (size_t i = 0; i < v->num_elements; i++) {
if (i > 0) putchar(',');
emit_newline_or_space(ctx);
repr_value_typed(v->elements[i], ctx, types);
}
ctx->depth--;
emit_newline_or_space(ctx);
putchar(']');
}
break;
case DTOB_KV_SET:
putchar('{');
if (v->num_pairs == 0 && v->num_elements == 0) {
putchar('}');
} else {
ctx->depth++;
int first = 1;
for (size_t i = 0; i < v->num_pairs; i++) {
if (!first) putchar(',');
first = 0;
emit_newline_or_space(ctx);
repr_literal_bytes(v->pairs[i].key, v->pairs[i].key_len);
printf(":");
if (ctx->indent > 0) putchar(' ');
repr_value_typed(v->pairs[i].value, ctx, types);
}
for (size_t i = 0; i < v->num_elements; i++) {
if (!first) putchar(',');
first = 0;
emit_newline_or_space(ctx);
repr_value_typed(v->elements[i], ctx, types);
}
ctx->depth--;
emit_newline_or_space(ctx);
putchar('}');
}
break;
case DTOB_CUSTOM: {
const char *name = types ? dtob_types_lookup(types, v->custom_code)
: NULL;
DtobCustomType *ct = types ? dtob_types_get(types, v->custom_code) : NULL;
if (ct && ct->is_struct) {
uint8_t cfg, cbg;
custom_color(v->custom_code, &cfg, &cbg);
if (name)
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", cfg, cbg, name);
else
printf("\033[1;38;5;%d;48;5;%dm custom:%d \033[0m", cfg, cbg, v->custom_code);
ctx->depth++;
for (size_t i = 0; i < v->num_elements; i++) {
emit_indent(ctx);
repr_value_typed(v->elements[i], ctx, types);
if (i + 1 < v->num_elements) putchar(',');
}
ctx->depth--;
break;
}
const char *inner_label = NULL;
if (v->inner_code && !(ct && ct->n_opcodes == 1)) {
inner_label = types ? dtob_types_lookup(types, v->inner_code) : NULL;
if (!inner_label) {
switch (v->inner_code) {
case DTOB_CODE_INT8: inner_label = "int8"; break;
case DTOB_CODE_INT16: inner_label = "int16"; break;
case DTOB_CODE_INT32: inner_label = "int32"; break;
case DTOB_CODE_INT64: inner_label = "int64"; break;
case DTOB_CODE_UINT8: inner_label = "uint8"; break;
case DTOB_CODE_UINT16: inner_label = "uint16"; break;
case DTOB_CODE_UINT32: inner_label = "uint32"; break;
case DTOB_CODE_UINT64: inner_label = "uint64"; break;
case DTOB_CODE_FLOAT: inner_label = "float"; break;
case DTOB_CODE_DOUBLE: inner_label = "double"; break;
case DTOB_CODE_RAW: inner_label = "raw"; break;
}
}
}
uint8_t cfg, cbg;
custom_color(v->custom_code, &cfg, &cbg);
int inner_is_custom = inner_label && v->inner_code >= DTOB_CUSTOM_MIN;
if (!v->data || v->data_len == 0) {
if (name)
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", cfg, cbg, name);
else
printf("\033[1;38;5;%d;48;5;%dm custom:%d \033[0m", cfg, cbg, v->custom_code);
if (inner_is_custom) {
uint8_t ifg, ibg;
custom_color(v->inner_code, &ifg, &ibg);
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", ifg, ibg, inner_label);
} else if (inner_label) {
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", cfg, cbg, inner_label);
}
if (v->num_elements > 0) {
ctx->depth++;
for (size_t i = 0; i < v->num_elements; i++) {
emit_indent(ctx);
repr_value_typed(v->elements[i], ctx, types);
if (i + 1 < v->num_elements) putchar(',');
}
ctx->depth--;
}
} else {
printf("\033[1;38;5;%d;48;5;%dm", cfg, cbg);
if (name)
printf(" %s", name);
else
printf(" custom:%d", v->custom_code);
if (inner_label && !inner_is_custom)
printf(":%s", inner_label);
printf(" \033[0m");
if (inner_is_custom) {
uint8_t ifg, ibg;
custom_color(v->inner_code, &ifg, &ibg);
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", ifg, ibg, inner_label);
}
printf(" ");
if (DTOB_CODE_IS_INT(v->inner_code) && v->data_len <= 8) {
int is_unsigned = DTOB_CODE_INT_UNSIGNED(v->inner_code);
uint64_t uval = 0;
for (size_t i = 0; i < v->data_len; i++)
uval = (uval << 8) | v->data[i];
if (is_unsigned) {
printf("%llu", (unsigned long long)uval);
} else {
int bits = (int)v->data_len * 8;
if (bits < 64 && (uval >> (bits - 1)) & 1)
uval |= ~(uint64_t)0 << bits;
printf("%lld", (long long)(int64_t)uval);
}
} else if (is_printable_utf8(v->data, v->data_len)) {
repr_literal_bytes(v->data, v->data_len);
} else {
printf("%zu bytes", v->data_len);
}
}
break;
}
}
}
void repr_value(const DtobValue *v, int indent, const DtobTypesHeader *types)
{
PrintCtx ctx = { indent, 0 };
repr_value_typed(v, &ctx, types);
printf("\n");
}
void repr_types(const DtobTypesHeader *types)
{
if (!types || types->count == 0) {
printf("(no types header)\n");
return;
}
for (size_t i = 0; i < types->count; i++) {
const DtobCustomType *ct = &types->entries[i];
uint8_t cfg, cbg;
custom_color(ct->code, &cfg, &cbg);
printf(" %d ", ct->code);
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", cfg, cbg,
ct->name ? ct->name : "?");
printf(" %s", ct->is_struct ? "struct" : "enum");
if (ct->n_opcodes == 0) {
printf(" nullable");
} else {
printf("(");
for (size_t j = 0; j < ct->n_opcodes; j++) {
if (j > 0) printf("|");
uint16_t op = ct->opcodes[j];
const char *opname = dtob_types_lookup(types, op);
if (opname) {
uint8_t ofg, obg;
custom_color(op, &ofg, &obg);
printf("\033[1;38;5;%d;48;5;%dm %s \033[0m", ofg, obg, opname);
} else {
switch (op) {
case DTOB_CODE_INT8: printf("int8"); break;
case DTOB_CODE_INT16: printf("int16"); break;
case DTOB_CODE_INT32: printf("int32"); break;
case DTOB_CODE_INT64: printf("int64"); break;
case DTOB_CODE_UINT8: printf("uint8"); break;
case DTOB_CODE_UINT16: printf("uint16"); break;
case DTOB_CODE_UINT32: printf("uint32"); break;
case DTOB_CODE_UINT64: printf("uint64"); break;
case DTOB_CODE_FLOAT: printf("float"); break;
case DTOB_CODE_DOUBLE: printf("double"); break;
case DTOB_CODE_RAW: printf("raw"); break;
default: printf("%d", op); break;
}
}
}
printf(")");
}
printf("\n");
}
}