librz/util: shared Unicode subscript formatting for bit-vectors and floats (#6418)
Consolidate the Unicode subscript notation used when rendering
bit-vector and float values (the subscript width on a bit-vector
constant, e.g. 0x2c followed by a subscript 8, and the format width
on a float, e.g. .f followed by a subscript 32) into one place, so
the RzIL Unicode exporter, the RzNum value printer, and the
RzNum->RzIL lift cannot drift apart.
RzUtil gains the single source of truth:
* rz_str_append_subscript() / rz_str_append_superscript() /
rz_str_subscript() render a number as Unicode subscript or
superscript digits;
* rz_bv_width_subscript() / rz_bv_as_unicode_string() build a
bit-vector's width subscript on top of the str helper;
* rz_float_format_subscript() renders a float format's width
subscript (16/32/64/80/128, with the decimal-format marker),
reusing the same digit renderer.
The RzIL Unicode exporter (il_export_string_unicode.c) is switched
fully onto these: every append_subscript() call site (bit-vector
constant width, cast length, memory indices) now goes through
rz_str_append_subscript(), and the hardcoded per-format subscript
macro is replaced by rz_float_format_subscript(). The exporter's
private subscript-digit table and ut32 glyph helper are removed, so
there is no longer a second, parallel implementation to keep in sync.
Signed-off-by: Anton Kochkov <anton.kochkov@gmail.com>
Co-authored-by: Anton Kochkov <anton.kochkov@gmail.com>
This commit is contained in:
parent
e91c636f00
commit
d3860590ee
7 changed files with 228 additions and 39 deletions
|
|
@ -6,8 +6,6 @@
|
|||
static bool il_op_pure_string_resolve(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb);
|
||||
static bool il_op_effect_string_resolve(RzILStringifyCtx *ctx, const RzILOpEffect *op, RzStrBuf *sb);
|
||||
|
||||
static const char *const subscript_digits[10] = { "₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉" };
|
||||
|
||||
#define UCD_ITE "↠"
|
||||
#define UCD_LET "="
|
||||
#define UCD_BOOL_FALSE "⊥"
|
||||
|
|
@ -180,34 +178,25 @@ static const char *const subscript_digits[10] = { "₀", "₁", "₂", "₃", "
|
|||
return rz_strbuf_append(sb, ")"); \
|
||||
} while (0);
|
||||
|
||||
#define sym_with_float_format(x, y) \
|
||||
(x) == RZ_FLOAT_IEEE754_BIN_32 ? y "₃₂" : (x) == RZ_FLOAT_IEEE754_BIN_64 ? y "₆₄" \
|
||||
: (x) == RZ_FLOAT_IEEE754_BIN_80 ? y "₈₀" \
|
||||
: (x) == RZ_FLOAT_IEEE754_BIN_128 ? y "₁₂₈" \
|
||||
: (x) == RZ_FLOAT_IEEE754_BIN_16 ? y "₁₆" \
|
||||
: (x) == RZ_FLOAT_IEEE754_DEC_64 ? y "ᵈ₆₄" \
|
||||
: (x) == RZ_FLOAT_IEEE754_DEC_128 ? y "ᵈ₁₂₈" \
|
||||
: ""
|
||||
|
||||
static bool append_ut32_glyph(RzStrBuf *sb, ut32 n, const char *const digits[10]) {
|
||||
char buffer[32] = { 0 };
|
||||
if (rz_strf(buffer, "%u", n) == NULL) {
|
||||
return false;
|
||||
// Combine a prefix with the float format's Unicode width subscript,
|
||||
// via the shared RzUtil renderer (rz_float_format_subscript) so the
|
||||
// notation matches the rest of the codebase. Returns a caller-owned
|
||||
// string; the caller must free it.
|
||||
static char *sym_with_float_format(RzFloatFormat format, const char *prefix) {
|
||||
char *sub = rz_float_format_subscript(format);
|
||||
if (!sub) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Each unicode superscript/subscript character is at most 3 bytes */
|
||||
const size_t len = strlen(buffer);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
int digit = buffer[i] - '0';
|
||||
if (!rz_strbuf_append(sb, digits[digit])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
char *out = rz_str_newf("%s%s", prefix, sub);
|
||||
free(sub);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Append a number as Unicode subscript digits, via the shared RzUtil
|
||||
// renderer so this exporter and the bit-vector / float value
|
||||
// formatters stay byte-for-byte identical.
|
||||
static bool append_subscript(RzStrBuf *sb, ut32 n) {
|
||||
return append_ut32_glyph(sb, n, subscript_digits);
|
||||
return rz_str_append_num_subscript(sb, n);
|
||||
}
|
||||
|
||||
static bool il_opdmp_var(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) {
|
||||
|
|
@ -270,14 +259,15 @@ static bool il_opdmp_bitv(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf
|
|||
const RzILOpArgsBv *opx = &op->op.bitv;
|
||||
char *num = rz_bv_as_hex_string(opx->value, false);
|
||||
return_false_if_fail(num);
|
||||
goto_if_fail(rz_strbuf_appendf(sb, "%s", num), fini);
|
||||
goto_if_fail(append_subscript(sb, opx->value->len), fini);
|
||||
// The hex value plus the width as a Unicode subscript, via the
|
||||
// shared RzUtil formatter (also used by the RzNum value printer)
|
||||
// so the two renderings of a bit-vector constant stay identical.
|
||||
char *uni = rz_bv_as_unicode_string(opx->value, num);
|
||||
free(num);
|
||||
return true;
|
||||
|
||||
fini:
|
||||
free(num);
|
||||
return false;
|
||||
return_false_if_fail(uni);
|
||||
bool ok = rz_strbuf_append(sb, uni);
|
||||
free(uni);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool il_opdmp_msb(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) {
|
||||
|
|
@ -380,8 +370,13 @@ static bool il_opdmp_float(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf
|
|||
switch (opx->bv->code) {
|
||||
default:
|
||||
return il_op_pure_string_resolve(ctx, opx->bv, sb);
|
||||
case RZ_IL_OP_BITV:
|
||||
return il_opdmp_bitv_float(ctx, opx->bv, sb, sym_with_float_format(opx->r, ".f"));
|
||||
case RZ_IL_OP_BITV: {
|
||||
char *sym = sym_with_float_format(opx->r, ".f");
|
||||
return_false_if_fail(sym);
|
||||
bool ok = il_opdmp_bitv_float(ctx, opx->bv, sb, sym);
|
||||
free(sym);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -433,14 +428,28 @@ static bool il_opdmp_fcast_sint(RzILStringifyCtx *ctx, const RzILOpPure *op, RzS
|
|||
|
||||
static bool il_opdmp_fcast_float(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) {
|
||||
const RzILOpArgsFCastfloat *opx = &op->op.fcast_float;
|
||||
const char *sym = sym_with_float_format(opx->format, UCD_FCAST_FLOAT);
|
||||
il_op_param_1_with_mode_format(sym, opx, bv, pure, mode, format);
|
||||
char *sym = sym_with_float_format(opx->format, UCD_FCAST_FLOAT);
|
||||
return_false_if_fail(sym);
|
||||
bool ok = rz_strbuf_append(sb, "(") &&
|
||||
il_op_pure_string_resolve(ctx, opx->bv, sb) &&
|
||||
rz_strbuf_appendf(sb, " %s ", sym) &&
|
||||
rz_strbuf_append(sb, rz_il_float_stringify_rmode(opx->mode)) &&
|
||||
rz_strbuf_append(sb, ")");
|
||||
free(sym);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool il_opdmp_fcast_sfloat(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) {
|
||||
const RzILOpArgsFCastsfloat *opx = &op->op.fcast_sfloat;
|
||||
const char *sym = sym_with_float_format(opx->format, UCD_FCAST_SFLOAT);
|
||||
il_op_param_1_with_mode_format(sym, opx, bv, pure, mode, format);
|
||||
char *sym = sym_with_float_format(opx->format, UCD_FCAST_SFLOAT);
|
||||
return_false_if_fail(sym);
|
||||
bool ok = rz_strbuf_append(sb, "(") &&
|
||||
il_op_pure_string_resolve(ctx, opx->bv, sb) &&
|
||||
rz_strbuf_appendf(sb, " %s ", sym) &&
|
||||
rz_strbuf_append(sb, rz_il_float_stringify_rmode(opx->mode)) &&
|
||||
rz_strbuf_append(sb, ")");
|
||||
free(sym);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool il_opdmp_fconvert(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ RZ_API void rz_bv_set_to_bytes_le(RZ_NONNULL const RzBitVector *bv, RZ_OUT RZ_NO
|
|||
RZ_API void rz_bv_set_to_bytes_be(RZ_NONNULL const RzBitVector *bv, RZ_OUT RZ_NONNULL ut8 *buf);
|
||||
RZ_API RZ_OWN char *rz_bv_as_string(RZ_NONNULL const RzBitVector *bv);
|
||||
RZ_API RZ_OWN char *rz_bv_as_hex_string(RZ_NONNULL const RzBitVector *bv, bool pad);
|
||||
RZ_API RZ_OWN char *rz_bv_width_subscript(ut32 width);
|
||||
RZ_API RZ_OWN char *rz_bv_as_unicode_string(RZ_NONNULL const RzBitVector *bv, RZ_NONNULL const char *value);
|
||||
|
||||
RZ_API ut32 rz_bv_len(RZ_NONNULL const RzBitVector *bv);
|
||||
RZ_API ut32 rz_bv_len_bytes(RZ_NONNULL const RzBitVector *bv);
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ static inline bool rz_float_is_neg_zero_long_double(long double zero) {
|
|||
#define IS_NEG_ZEROLD(z) rz_float_is_neg_zero_long_double(z)
|
||||
|
||||
RZ_API ut32 rz_float_get_format_info(RzFloatFormat format, RzFloatInfo which_info);
|
||||
RZ_API RZ_OWN char *rz_float_format_subscript(RzFloatFormat format);
|
||||
RZ_API void rz_float_fini(RZ_NONNULL RzFloat *f);
|
||||
RZ_API void rz_float_free(RZ_NULLABLE RzFloat *f);
|
||||
RZ_API bool rz_float_init(RZ_NONNULL RzFloat *f, RzFloatFormat format);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "rz_list.h"
|
||||
#include <rz_vector.h>
|
||||
#include "rz_types.h"
|
||||
#include "rz_strbuf.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
|
@ -255,6 +256,9 @@ RZ_API RZ_OWN char *rz_str_append(RZ_OWN RZ_NULLABLE char *ptr, const char *stri
|
|||
RZ_API char *rz_str_append_owned(char *ptr, char *string);
|
||||
RZ_API RZ_OWN char *rz_str_appendf(RZ_OWN RZ_NULLABLE char *ptr, const char *fmt, ...) RZ_PRINTF_CHECK(2, 3);
|
||||
RZ_API char *rz_str_appendch(char *x, char y);
|
||||
RZ_API bool rz_str_append_num_subscript(RZ_NONNULL RzStrBuf *sb, ut32 n);
|
||||
RZ_API bool rz_str_append_num_superscript(RZ_NONNULL RzStrBuf *sb, ut32 n);
|
||||
RZ_API RZ_OWN char *rz_str_num_subscript(ut32 n);
|
||||
RZ_API void rz_str_case(char *str, bool up);
|
||||
RZ_API void rz_str_trim_path(char *s);
|
||||
RZ_API ut8 rz_str_contains_macro(const char *input_value);
|
||||
|
|
|
|||
|
|
@ -188,6 +188,47 @@ RZ_API RZ_OWN char *rz_bv_as_hex_string(RZ_NONNULL const RzBitVector *bv, bool p
|
|||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a width as a run of Unicode subscript digits.
|
||||
*
|
||||
* This is the bit-width annotation used when rendering a bit-vector
|
||||
* in Unicode form (e.g. the subscript 8 in 0x2c with a trailing 8).
|
||||
* Shared so the RzIL Unicode export and the RzNum value printer
|
||||
* cannot drift apart.
|
||||
*
|
||||
* \param width The width to render.
|
||||
* \return A freshly-allocated, caller-owned string, or NULL on
|
||||
* allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_bv_width_subscript(ut32 width) {
|
||||
return rz_str_num_subscript(width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a pre-formatted bit-vector \p value followed by the
|
||||
* bit-vector's width as a Unicode subscript.
|
||||
*
|
||||
* \p value is the already-stringified value (for instance the output
|
||||
* of rz_bv_as_hex_string() or rz_bv_as_string()); only the width
|
||||
* subscript is appended here, so the caller controls the value's
|
||||
* base and padding. The result is a freshly-allocated, caller-owned
|
||||
* string, e.g. "0x2c" followed by a subscript 8.
|
||||
*
|
||||
* \param bv The bit-vector whose width is annotated. Must be non-NULL.
|
||||
* \param value The pre-formatted value string. Must be non-NULL.
|
||||
* \return The combined string, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_bv_as_unicode_string(RZ_NONNULL const RzBitVector *bv, RZ_NONNULL const char *value) {
|
||||
rz_return_val_if_fail(bv && value, NULL);
|
||||
char *sub = rz_bv_width_subscript(rz_bv_len(bv));
|
||||
if (!sub) {
|
||||
return NULL;
|
||||
}
|
||||
char *out = rz_str_newf("%s%s", value, sub);
|
||||
free(sub);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a bitvector
|
||||
* \param bv RzBitVector, pointer to the source bitvector
|
||||
|
|
|
|||
|
|
@ -1932,3 +1932,56 @@ RZ_API RZ_OWN RzBitVector *rz_float_round_significant(bool sign, RzBitVector *si
|
|||
RZ_API RZ_OWN RzFloat *rz_float_round_bv_and_pack(bool sign, st32 exp, RzBitVector *sig, RzFloatFormat format, RzFloatRMode mode) {
|
||||
return round_float_bv_new(sign, exp, sig, format, format, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Render a float format's width as a Unicode subscript string.
|
||||
*
|
||||
* Mirrors the bit-vector width subscript (rz_bv_width_subscript): the
|
||||
* total bit width of \p format is rendered as Unicode subscript
|
||||
* digits, with a leading "d" subscript marker for the decimal
|
||||
* formats. For example IEEE-754 binary32 yields the subscript "32"
|
||||
* and decimal64 yields "d64". This is the single source of truth for
|
||||
* the float-format subscript shared by value formatting and the RzIL
|
||||
* Unicode exporter.
|
||||
*
|
||||
* \param format The float format to annotate.
|
||||
* \return A freshly-allocated, caller-owned string, or NULL on
|
||||
* allocation failure or an unknown format.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_float_format_subscript(RzFloatFormat format) {
|
||||
// The decimal formats are not fully implemented in RzFloat
|
||||
// (rz_float_get_format_info returns 0 for them), so their widths
|
||||
// are spelled out here; they render with a leading "d" marker.
|
||||
ut32 total;
|
||||
bool is_decimal = false;
|
||||
switch (format) {
|
||||
case RZ_FLOAT_IEEE754_DEC_64:
|
||||
total = 64;
|
||||
is_decimal = true;
|
||||
break;
|
||||
case RZ_FLOAT_IEEE754_DEC_128:
|
||||
total = 128;
|
||||
is_decimal = true;
|
||||
break;
|
||||
default:
|
||||
total = rz_float_get_format_info(format, RZ_FLOAT_INFO_TOTAL_LEN);
|
||||
break;
|
||||
}
|
||||
if (!total) {
|
||||
return NULL;
|
||||
}
|
||||
RzStrBuf sb;
|
||||
rz_strbuf_init(&sb);
|
||||
// Decimal formats carry a "d" marker (U+1D48 modifier letter
|
||||
// small d) before the width to distinguish them from the binary
|
||||
// formats, matching the RzIL Unicode exporter's notation.
|
||||
if (is_decimal && !rz_strbuf_append(&sb, "\u1d48")) {
|
||||
rz_strbuf_fini(&sb);
|
||||
return NULL;
|
||||
}
|
||||
if (!rz_str_append_num_subscript(&sb, total)) {
|
||||
rz_strbuf_fini(&sb);
|
||||
return NULL;
|
||||
}
|
||||
return rz_strbuf_drain_nofree(&sb);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4610,3 +4610,82 @@ RZ_API bool rz_string_enc_requires_scanning(RzStrEnc enc) {
|
|||
rz_warn_if_reached();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unicode subscript digits U+2080..U+2089 and superscript digits
|
||||
// (U+2070, U+00B9, U+00B2, U+00B3, U+2074..U+2079), indexed by the
|
||||
// digit value 0..9. These are the single source of truth for the
|
||||
// subscript/superscript number rendering shared by the bit-vector and
|
||||
// float formatters and the RzIL Unicode exporter.
|
||||
static const char *const rz_str_subscript_digits[10] = {
|
||||
"\u2080", "\u2081", "\u2082", "\u2083", "\u2084",
|
||||
"\u2085", "\u2086", "\u2087", "\u2088", "\u2089"
|
||||
};
|
||||
|
||||
static const char *const rz_str_superscript_digits[10] = {
|
||||
"\u2070", "\u00b9", "\u00b2", "\u00b3", "\u2074",
|
||||
"\u2075", "\u2076", "\u2077", "\u2078", "\u2079"
|
||||
};
|
||||
|
||||
static bool str_append_glyph_digits(RzStrBuf *sb, ut32 n,
|
||||
const char *const digits[10]) {
|
||||
char buf[16];
|
||||
rz_strf(buf, "%u", n);
|
||||
for (const char *d = buf; *d; d++) {
|
||||
if (!rz_strbuf_append(sb, digits[*d - '0'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Append \p n rendered as Unicode subscript digits to \p sb.
|
||||
*
|
||||
* For example 32 becomes the subscript "32" (U+2083 U+2082). This is
|
||||
* the shared renderer for the bit-width/format-width subscripts used
|
||||
* by bit-vector and float value formatting and by the RzIL Unicode
|
||||
* exporter, so all of those stay byte-for-byte identical.
|
||||
*
|
||||
* \param sb Destination string buffer.
|
||||
* \param n The number to render.
|
||||
* \return true on success, false on allocation failure.
|
||||
*/
|
||||
RZ_API bool rz_str_append_num_subscript(RZ_NONNULL RzStrBuf *sb, ut32 n) {
|
||||
rz_return_val_if_fail(sb, false);
|
||||
return str_append_glyph_digits(sb, n, rz_str_subscript_digits);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Append \p n rendered as Unicode superscript digits to \p sb.
|
||||
*
|
||||
* The superscript counterpart of rz_str_append_num_subscript(); shared so
|
||||
* the RzIL Unicode exporter and any other consumer render run-length
|
||||
* style annotations identically.
|
||||
*
|
||||
* \param sb Destination string buffer.
|
||||
* \param n The number to render.
|
||||
* \return true on success, false on allocation failure.
|
||||
*/
|
||||
RZ_API bool rz_str_append_num_superscript(RZ_NONNULL RzStrBuf *sb, ut32 n) {
|
||||
rz_return_val_if_fail(sb, false);
|
||||
return str_append_glyph_digits(sb, n, rz_str_superscript_digits);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Render \p n as a freshly-allocated Unicode subscript string.
|
||||
*
|
||||
* Convenience wrapper around rz_str_append_num_subscript() for callers
|
||||
* that want an owned string rather than appending to a buffer.
|
||||
*
|
||||
* \param n The number to render.
|
||||
* \return A caller-owned string, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_str_num_subscript(ut32 n) {
|
||||
RzStrBuf sb;
|
||||
rz_strbuf_init(&sb);
|
||||
if (!rz_str_append_num_subscript(&sb, n)) {
|
||||
rz_strbuf_fini(&sb);
|
||||
return NULL;
|
||||
}
|
||||
return rz_strbuf_drain_nofree(&sb);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue