0536b42693
- add `js_snprintf`, `js_printf`... to handle extra conversions: - support for wxx length modifier - support for `%b` and `%B` - `%oa` and `%#oa` to convert `JSAtom` values - `%ps` to convert `JSString` values - add `dbuf_vprintf_fun` replaceable `dbuf_printf` handler - change `JS_DumpString` to `JS_FormatString` to convert `JSSAtom` to quoted strings - change `JS_AtomGetStrRT` to `JS_FormatAtom` to convert `JSAtom` to strings - change `JS_AtomGetStr` to return direct string pointer for builtin atoms - remove `print_atom` - use custom conversions for trace messages and error messages - add support for `%b`, `%B` and `w` length modifier in `std.printf` - remove error handlers: `JS_ThrowTypeErrorAtom` and `JS_ThrowSyntaxErrorAtom` - add `is_lower_ascii()` and `to_lower_ascii()` - add floating point conversions and wide string conversions - unbreak compilation: prevent name collision on pow10 - minimize `vsnprintf` calls in `dbuf_vprintf_default`
1092 lines
33 KiB
C
1092 lines
33 KiB
C
/*
|
|
* QuickJS Javascript printf functions
|
|
*
|
|
* Copyright (c) 2024 Charlie Gordon
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include <limits.h>
|
|
|
|
#ifdef TEST_QUICKJS
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include "cutils.h"
|
|
//#include "cutils.c"
|
|
#include "quickjs-printf.h"
|
|
typedef union JSFloat64Union {
|
|
double d;
|
|
uint64_t u64;
|
|
uint32_t u32[2];
|
|
} JSFloat64Union;
|
|
#define JS_GET_CTX_RT(ctx) 0
|
|
#define JS_GET_RT_RT(rt) 0
|
|
#define JS_GET_DBUF_RT(s) 0
|
|
//#define js_malloc_rt(rt, size) malloc(size)
|
|
//#define js_free_rt(rt, ptr) free(ptr)
|
|
#else
|
|
#define JS_GET_RT_RT(rt) (rt)
|
|
#define JS_GET_CTX_RT(ctx) ((ctx)->rt)
|
|
#define JS_GET_DBUF_RT(s) ((s)->opaque)
|
|
#endif
|
|
|
|
/* Rounding modes: There are six possible rounding modes for values
|
|
exactly half way between 2 numbers: 1.5 2.5 -1.5 -2.5
|
|
ROUND_HALF_UP Round away from zero: 2 3 -2 -3
|
|
ROUND_HALF_DOWN Round towards zero: 1 2 -1 -2
|
|
ROUND_HALF_EVEN Round to nearest even value: 2 2 -2 -2
|
|
ROUND_HALF_ODD Round to nearest odd value: 1 3 -1 -3
|
|
ROUND_HALF_NEXT Round toward +Infinity: 2 3 -1 -2
|
|
ROUND_HALF_PREV Round toward -Infinity: 1 2 -2 -3
|
|
ECMA specifies rounding as ROUND_HALF_UP.
|
|
Standard C printf specifies rounding should be performed according
|
|
to fegetround(), which defaults to FE_TONEAREST, which should
|
|
correspond to ROUND_HALF_EVEN.
|
|
This implementation only supports the first 4 modes.
|
|
*/
|
|
enum {
|
|
FLAG_ROUND_HALF_ODD = 0,
|
|
FLAG_ROUND_HALF_EVEN = 1,
|
|
FLAG_ROUND_HALF_UP = 2,
|
|
FLAG_ROUND_HALF_NEXT = 3,
|
|
FLAG_ROUND_HALF_DOWN = 4,
|
|
FLAG_ROUND_HALF_PREV = 5,
|
|
FLAG_STRIP_ZEROES = 0x10,
|
|
FLAG_FORCE_DOT = 0x20,
|
|
};
|
|
|
|
typedef struct JSFormatContext {
|
|
JSRuntime *rt;
|
|
void *ptr;
|
|
size_t size, pos;
|
|
int (*write)(struct JSFormatContext *fcp, const char *str, size_t len);
|
|
} JSFormatContext;
|
|
|
|
enum {
|
|
FLAG_LEFT = 1, FLAG_HASH = 2, FLAG_ZERO = 4,
|
|
FLAG_WIDTH = 8, FLAG_PREC = 16,
|
|
};
|
|
|
|
/*---- floating point conversions ----*/
|
|
|
|
// handle 9 decimal digits at a time */
|
|
#define COMP10 1000000000
|
|
#define COMP10_LEN 9
|
|
#define COMP10_MAX_SHIFT 34 // 64 - ceil(log2(1e9))
|
|
|
|
/* Initialize a bignum from a 64-bit unsigned int */
|
|
static size_t comp10_init(uint32_t *num, uint64_t mant) {
|
|
size_t i = 0;
|
|
while (mant >= COMP10) {
|
|
num[i++] = mant % COMP10;
|
|
mant /= COMP10;
|
|
}
|
|
num[i++] = (uint32_t)mant;
|
|
return i;
|
|
}
|
|
|
|
/* Shift a bignum by a bit count in 0..COMP10_MAX_SHIFT */
|
|
static size_t comp10_shift(uint32_t *num, size_t plen, int shift) {
|
|
uint64_t carry = 0;
|
|
size_t i;
|
|
for (i = 0; i < plen; i++) {
|
|
carry += (uint64_t)num[i] << shift;
|
|
num[i] = carry % COMP10;
|
|
carry /= COMP10;
|
|
}
|
|
if (carry) {
|
|
if (carry >= COMP10) {
|
|
num[plen++] = carry % COMP10;
|
|
carry /= COMP10;
|
|
}
|
|
num[plen++] = (uint32_t)carry;
|
|
}
|
|
return plen;
|
|
}
|
|
|
|
/* Multiply a bignum by a constant <= UINT32_MAX */
|
|
static size_t comp10_multiply(uint32_t *num, size_t plen, uint32_t mul) {
|
|
uint64_t carry = 0;
|
|
size_t i;
|
|
for (i = 0; i < plen; i++) {
|
|
carry += num[i] * (uint64_t)mul;
|
|
num[i] = carry % COMP10;
|
|
carry /= COMP10;
|
|
}
|
|
if (carry) {
|
|
if (carry >= COMP10) {
|
|
num[plen++] = carry % COMP10;
|
|
carry /= COMP10;
|
|
}
|
|
num[plen++] = (uint32_t)carry;
|
|
}
|
|
return plen;
|
|
}
|
|
|
|
/* Compute the number of decimal digits in a normalized non-zero COMP10 unit */
|
|
static int digits_count(uint32_t val) {
|
|
/* this code hopefully branchless */
|
|
return (1 + (val > 9)
|
|
+ (val > 99)
|
|
+ (val > 999)
|
|
+ (val > 9999)
|
|
+ (val > 99999)
|
|
+ (val > 999999)
|
|
+ (val > 9999999)
|
|
#if COMP10_LEN > 8
|
|
+ (val > 99999999)
|
|
#endif
|
|
);
|
|
}
|
|
|
|
/* Powers of 5 less than UINT32_MAX */
|
|
static uint32_t const pow5_table[14] = {
|
|
1UL,
|
|
5UL,
|
|
5UL*5UL,
|
|
5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL,
|
|
};
|
|
|
|
/* Powers of 10 less than UINT32_MAX */
|
|
static uint32_t const pow10_table[10] = {
|
|
1UL,
|
|
10UL,
|
|
100UL,
|
|
1000UL,
|
|
10000UL,
|
|
100000UL,
|
|
1000000UL,
|
|
10000000UL,
|
|
100000000UL,
|
|
1000000000UL,
|
|
};
|
|
|
|
/* Increment a bignum by a value `inc` times COMP10 power `from`,
|
|
assuming from <= plen */
|
|
static size_t comp10_inc(uint32_t *num, size_t plen, uint32_t inc, size_t from) {
|
|
uint32_t carry = inc;
|
|
size_t i;
|
|
for (i = from; i < plen; i++) {
|
|
if ((num[i] += carry) < COMP10)
|
|
return plen;
|
|
num[i] -= COMP10;
|
|
carry = 1;
|
|
}
|
|
if (carry) {
|
|
num[plen++] = carry;
|
|
}
|
|
return plen;
|
|
}
|
|
|
|
// minimum buffer length is 1077 bytes for printf("%.1074f", 5e-324)
|
|
// including the null terminator
|
|
static int js_format_f(double value, char dest[minimum_length(2+1074+1)],
|
|
int prec, int use_exp,
|
|
int fflags, size_t *trailing_zeroes, int *pexp)
|
|
{
|
|
uint32_t digits[(1022 + 52 + COMP10_LEN - 1 - 297) / COMP10_LEN];
|
|
JSFloat64Union u;
|
|
uint64_t mant;
|
|
char *p1, *p = dest;
|
|
size_t plen;
|
|
int exp2, i, j, numd, maxd, spt;
|
|
unsigned int val;
|
|
|
|
/* assuming IEEE 64-bit format */
|
|
u.d = value;
|
|
exp2 = (int)((u.u64 >> 52U) & 0x07FFU) - 1023;
|
|
mant = u.u64 & ((1ULL << 52U) - 1);
|
|
// uncomment to support FLAG_ROUND_HALF_NEXT and FLAG_ROUND_HALF_PREV
|
|
// fflags += ((fflags & 2) - (fflags & 4)) & -((int)(u.u64 >> 63) & fflags & 1);
|
|
if (exp2 == -1023) {
|
|
exp2++;
|
|
if (mant == 0)
|
|
goto has_zero;
|
|
} else {
|
|
mant |= (1ULL << 52U);
|
|
}
|
|
j = ctz64(mant);
|
|
mant >>= j;
|
|
exp2 -= 52 - j;
|
|
if (exp2 >= 0) {
|
|
/* integer value: compute mant * 2**exp2 */
|
|
#if 1
|
|
j += 59 - 52;
|
|
if (j >= exp2)
|
|
j = exp2;
|
|
mant <<= j;
|
|
exp2 -= j;
|
|
#else
|
|
if (exp2 + 52 - j < 64) {
|
|
/* if value fits in a uint64_t, shift it */
|
|
mant <<= exp2;
|
|
exp2 = 0;
|
|
}
|
|
#endif
|
|
plen = comp10_init(digits, mant);
|
|
while (exp2 > 0) {
|
|
int k = exp2 >= COMP10_MAX_SHIFT ? COMP10_MAX_SHIFT : exp2;
|
|
plen = comp10_shift(digits, plen, k);
|
|
exp2 -= k;
|
|
}
|
|
} else {
|
|
/* fractional part: compute power of 2 and multiply by mant */
|
|
/* this is faster than using 32-bit digits and multiply by 10**9 */
|
|
/* -exp2 is the number of bits after the decimal point. */
|
|
/* multiply mant by 5 to the power of -exp2 */
|
|
int exp;
|
|
plen = comp10_init(digits, mant);
|
|
for (exp = -exp2; exp > 0;) {
|
|
int k = exp >= 13 ? 13 : exp;
|
|
plen = comp10_multiply(digits, plen, pow5_table[k]);
|
|
exp -= k;
|
|
}
|
|
}
|
|
/* round converted number according to prec:
|
|
if !use_exp, the number is an integer, no rounding necessary
|
|
if use_exp, add 5*pow(10, numd-1-prec) unless numd==1+prec and
|
|
digit 1 has even parity.
|
|
*/
|
|
val = digits[--plen]; // leading digits: 1..COMP10_LEN
|
|
j = digits_count(val);
|
|
numd = j + plen * COMP10_LEN; // number of significant digits
|
|
i = numd + exp2; // number of digits before the .
|
|
|
|
if (use_exp) {
|
|
maxd = prec + 1;
|
|
} else {
|
|
maxd = prec + i;
|
|
/* if maxd < 0, the result is 0 */
|
|
if (maxd < 0) {
|
|
has_zero:
|
|
*dest = '0';
|
|
*pexp = 0;
|
|
i = 1;
|
|
if (fflags & FLAG_STRIP_ZEROES)
|
|
prec = 0;
|
|
*trailing_zeroes = prec;
|
|
if (prec || (fflags & FLAG_FORCE_DOT))
|
|
dest[i++] = '.';
|
|
return i;
|
|
}
|
|
}
|
|
if (maxd < numd) {
|
|
/* round converted number to maxd digits */
|
|
unsigned int trunc = numd - maxd;
|
|
/* add 0.5 * 10**trunc unless rounding to even */
|
|
// initialize trail to non zero if FLAG_ROUND_HALF_UP
|
|
uint32_t inc, half, low, trail = fflags & FLAG_ROUND_HALF_UP;
|
|
size_t start = 0;
|
|
while (trunc > COMP10_LEN) {
|
|
trail |= digits[start++];
|
|
trunc -= COMP10_LEN;
|
|
}
|
|
inc = pow10_table[trunc]; // trunc is in range 1..COMP10_LEN
|
|
half = inc / 2;
|
|
low = digits[start] % inc;
|
|
// round to nearest, tie to even
|
|
if (low > half ||
|
|
(low == half && !(fflags & FLAG_ROUND_HALF_DOWN) &&
|
|
(trail ||
|
|
(trunc == COMP10_LEN ?
|
|
digits[start + 1] % 2 == (fflags & FLAG_ROUND_HALF_EVEN) :
|
|
digits[start] / inc % 2 == (fflags & FLAG_ROUND_HALF_EVEN)))))
|
|
{
|
|
/* add inc to the number */
|
|
plen = comp10_inc(digits, plen + 1, inc, start) - 1;
|
|
if (val != digits[plen]) {
|
|
val = digits[plen];
|
|
j = digits_count(val);
|
|
numd = j + plen * COMP10_LEN;
|
|
i = numd + exp2;
|
|
if (use_exp) {
|
|
maxd = prec + 1;
|
|
} else {
|
|
maxd = prec + i;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
maxd = numd;
|
|
}
|
|
if (use_exp) {
|
|
spt = 1;
|
|
*pexp = i - 1;
|
|
} else {
|
|
while (i <= 0) {
|
|
*p++ = '0';
|
|
i++;
|
|
}
|
|
spt = i;
|
|
*pexp = 0;
|
|
}
|
|
/* only output maxd digits? */
|
|
p1 = p += j;
|
|
while (val > 9) {
|
|
*--p1 = '0' + val % 10;
|
|
val /= 10;
|
|
}
|
|
*--p1 = (char)('0' + val);
|
|
maxd -= j;
|
|
for (i = plen; maxd > 0 && i --> 0; maxd -= COMP10_LEN) {
|
|
val = digits[i];
|
|
p1 = p += COMP10_LEN;
|
|
for (j = 0; j < COMP10_LEN - 1; j++) {
|
|
*--p1 = '0' + val % 10;
|
|
val /= 10;
|
|
}
|
|
*--p1 = (char)('0' + val);
|
|
}
|
|
p += maxd;
|
|
i = p - dest;
|
|
|
|
/* strip trailing zeroes after the decimal point */
|
|
while (i > spt && dest[i - 1] == '0')
|
|
i--;
|
|
if (fflags & FLAG_STRIP_ZEROES)
|
|
prec = i - spt;
|
|
*trailing_zeroes = spt + prec - i;
|
|
/* insert decimal point */
|
|
if (prec || (fflags & FLAG_FORCE_DOT)) {
|
|
for (j = ++i; j --> spt;)
|
|
dest[j] = dest[j - 1];
|
|
dest[spt] = '.';
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static int js_format_a(double d, char dest[minimum_length(2+13+6+1)],
|
|
int prec, const char *digits,
|
|
int fflags, size_t *trailing_zeroes, int *pexp)
|
|
{
|
|
JSFloat64Union u;
|
|
int shift, exp2, ndig, zeroes, tzcount;
|
|
uint64_t mant, half;
|
|
char *p = dest;
|
|
|
|
u.d = d;
|
|
|
|
/* extract mantissa and binary exponent */
|
|
mant = u.u64 & ((1ULL << 52) - 1);
|
|
exp2 = (u.u64 >> 52) & 0x07FF;
|
|
// uncomment to support FLAG_ROUND_HALF_NEXT and FLAG_ROUND_HALF_PREV
|
|
// fflags += ((fflags & 2) - (fflags & 4)) & -((int)(u.u64 >> 63) & fflags & 1);
|
|
if (exp2 == 0) {
|
|
/* subnormal */
|
|
ndig = 0;
|
|
tzcount = 0;
|
|
if (mant == 0)
|
|
goto next;
|
|
shift = clz64(mant) - 11;
|
|
mant <<= shift;
|
|
exp2 = 1 - shift;
|
|
}
|
|
mant |= 1ULL << 52;
|
|
exp2 -= 1023;
|
|
tzcount = ctz64(mant);
|
|
ndig = 13 - (tzcount >> 2);
|
|
next:
|
|
*pexp = exp2;
|
|
zeroes = 0;
|
|
if (prec >= 0) {
|
|
if (prec >= ndig) {
|
|
zeroes = prec - ndig;
|
|
} else {
|
|
// round to nearest according to flags
|
|
shift = 52 - prec * 4 - 1;
|
|
ndig = prec;
|
|
half = (1ULL << shift) -
|
|
!(tzcount == shift && !(fflags & FLAG_ROUND_HALF_DOWN) &&
|
|
((fflags & FLAG_ROUND_HALF_UP) ||
|
|
(mant >> (shift + 1)) % 2 == (fflags & FLAG_ROUND_HALF_EVEN)));
|
|
mant += half;
|
|
}
|
|
}
|
|
*trailing_zeroes = zeroes;
|
|
*p++ = (char)('0' + (int)(mant >> 52));
|
|
if ((fflags & FLAG_FORCE_DOT) | zeroes | ndig) {
|
|
*p++ = '.';
|
|
for (shift = 52 - 4; ndig --> 0; shift -= 4)
|
|
*p++ = digits[(size_t)(mant >> shift) & 15];
|
|
}
|
|
return p - dest;
|
|
}
|
|
|
|
// construct the exponent. dest minimum length is 7 bytes
|
|
// (including space for the null terminator, which is not set here)
|
|
static size_t js_format_exp(char *dest, int exp, char pref, int min_digits) {
|
|
size_t i, len;
|
|
dest[0] = pref;
|
|
dest[1] = '+';
|
|
if (exp < 0) {
|
|
dest[1] = '-';
|
|
exp = -exp;
|
|
}
|
|
len = 3 + (exp >= 1000) + (exp >= 100) + (exp >= 10 || min_digits > 1);
|
|
for (i = len; i --> 3;) {
|
|
dest[i] = (char)('0' + exp % 10);
|
|
exp /= 10;
|
|
}
|
|
dest[i] = (char)('0' + exp);
|
|
return len;
|
|
}
|
|
|
|
#if 1
|
|
static size_t js_format_spaces(JSFormatContext *fcp, size_t count)
|
|
{
|
|
static char const buf[16] = " ";
|
|
size_t len = count;
|
|
|
|
while (count > 0) {
|
|
size_t chunk = count < sizeof(buf) ? count : sizeof(buf);
|
|
fcp->write(fcp, buf, chunk);
|
|
count -= chunk;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static size_t js_format_zeroes(JSFormatContext *fcp, size_t count)
|
|
{
|
|
static char const buf[16] = "0000000000000000";
|
|
size_t len = count;
|
|
|
|
while (count > 0) {
|
|
size_t chunk = count < sizeof(buf) ? count : sizeof(buf);
|
|
fcp->write(fcp, buf, chunk);
|
|
count -= chunk;
|
|
}
|
|
return len;
|
|
}
|
|
#else
|
|
static size_t js_format_run(JSFormatContext *fcp, char c, size_t count)
|
|
{
|
|
char buf[128];
|
|
size_t len = 0;
|
|
|
|
while (count > 0) {
|
|
size_t chunk = count < sizeof(buf) ? count : sizeof(buf);
|
|
memset(buf, c, chunk);
|
|
fcp->write(fcp, buf, chunk);
|
|
len += chunk;
|
|
count -= chunk;
|
|
}
|
|
return len;
|
|
}
|
|
#define js_format_spaces(fcp, count) js_format_run(fcp, ' ', count)
|
|
#define js_format_zeroes(fcp, count) js_format_run(fcp, '0', count)
|
|
#endif
|
|
|
|
static int js_format_str(JSFormatContext *fcp, int flags, int width, int prec, const char *str)
|
|
{
|
|
size_t slen, pad, pos;
|
|
|
|
if (flags & FLAG_PREC) {
|
|
// emulate slen = strnlen(str, prec);
|
|
for (slen = 0; slen < (size_t)prec && str[slen]; slen++)
|
|
continue;
|
|
} else {
|
|
slen = strlen(str);
|
|
}
|
|
|
|
pos = pad = 0;
|
|
if (slen < (size_t)width) {
|
|
pad = width - slen;
|
|
if (!(flags & FLAG_LEFT)) {
|
|
/* left pad with spaces */
|
|
pos += js_format_spaces(fcp, pad);
|
|
pad = 0;
|
|
}
|
|
}
|
|
pos += fcp->write(fcp, str, slen);
|
|
if (pad)
|
|
pos += js_format_spaces(fcp, pad);
|
|
return pos;
|
|
}
|
|
|
|
static int js_format_wstr(JSFormatContext *fcp, int flags, int width, int prec,
|
|
char *dest, size_t size, const wchar_t *wstr)
|
|
{
|
|
size_t i, j, k, pos = 0;
|
|
uint32_t c;
|
|
|
|
if (!(flags & FLAG_PREC))
|
|
prec = INT_MAX;
|
|
pos = 0;
|
|
if (width > 0) {
|
|
// compute the converted string length
|
|
for (i = j = 0; (c = wstr[i++]) != 0;) {
|
|
if (sizeof(wchar_t) == 2 && is_hi_surrogate(c) && is_lo_surrogate(wstr[i]))
|
|
c = from_surrogate(c, wstr[i++]);
|
|
j += utf8_encode((uint8_t *)dest, c);
|
|
}
|
|
if (j < (size_t)width && !(flags & FLAG_LEFT)) {
|
|
/* left pad with spaces */
|
|
pos += js_format_spaces(fcp, width - j);
|
|
}
|
|
}
|
|
|
|
for (i = j = 0; (c = wstr[i++]) != 0;) {
|
|
if (sizeof(wchar_t) == 2 && is_hi_surrogate(c) && is_lo_surrogate(wstr[i]))
|
|
c = from_surrogate(c, wstr[i++]);
|
|
if (j + UTF8_CHAR_LEN_MAX > size) {
|
|
pos += fcp->write(fcp, dest, j);
|
|
j = 0;
|
|
}
|
|
k = utf8_encode((uint8_t *)dest + j, c);
|
|
if ((size_t)prec < k)
|
|
break;
|
|
prec -= k;
|
|
j += k;
|
|
}
|
|
pos += fcp->write(fcp, dest, j);
|
|
if (pos < (size_t)width)
|
|
pos += js_format_spaces(fcp, width - pos);
|
|
return pos;
|
|
}
|
|
|
|
static int js_format(JSFormatContext *fcp, const char *fmt, va_list ap)
|
|
{
|
|
char buf[1080]; // used for integer and floating point conversions
|
|
char prefix[4]; // sign and/or 0x, 0X, 0b or 0B prefixes
|
|
char suffix[8]; // floating point exponent, range 'p-1074 to p+1023'
|
|
size_t pos = 0, i, slen, prefix_len, suffix_len, leading_zeroes, trailing_zeroes, prec, pad;
|
|
const char *str;
|
|
const char *digits;
|
|
char cc, lc;
|
|
int width, flags, length, wc, shift;
|
|
unsigned ww;
|
|
uint64_t uval, signbit, mask;
|
|
double val;
|
|
int exp, fprec, fflags;
|
|
|
|
str = fmt;
|
|
for (;;) {
|
|
cc = *fmt++;
|
|
if (cc != '%' && cc != '\0')
|
|
continue;
|
|
slen = fmt - str - 1;
|
|
if (slen)
|
|
pos += fcp->write(fcp, str, slen);
|
|
if (cc == '\0')
|
|
break;
|
|
/* quick dispatch for special and common formats */
|
|
switch (*fmt) {
|
|
case '%':
|
|
str = fmt++;
|
|
continue;
|
|
case 'd':
|
|
str = buf;
|
|
slen = i32toa(buf, va_arg(ap, int));
|
|
goto hasstr;
|
|
case 's':
|
|
str = va_arg(ap, const char *);
|
|
if (!str)
|
|
str = "(null)";
|
|
slen = strlen(str);
|
|
hasstr:
|
|
pos += fcp->write(fcp, str, slen);
|
|
str = ++fmt;
|
|
continue;
|
|
}
|
|
prefix[0] = '\0';
|
|
flags = 0;
|
|
length = sizeof(unsigned int) * CHAR_BIT;
|
|
str = fmt - 1;
|
|
prefix_len = leading_zeroes = slen = trailing_zeroes = suffix_len = prec = width = 0;
|
|
moreflags:
|
|
switch (cc = *fmt) {
|
|
case ' ':
|
|
case '+': fmt++; prefix[0] |= cc; goto moreflags; /* assuming ASCII */
|
|
case '-': fmt++; flags |= FLAG_LEFT; goto moreflags;
|
|
case '#': fmt++; flags |= FLAG_HASH; goto moreflags;
|
|
case '0': fmt++; flags |= FLAG_ZERO; goto moreflags;
|
|
case '*':
|
|
//flags |= FLAG_WIDTH;
|
|
fmt++;
|
|
wc = va_arg(ap, int);
|
|
if (wc < 0) {
|
|
flags |= FLAG_LEFT;
|
|
if (wc != INT_MIN)
|
|
width = -wc;
|
|
} else {
|
|
width = wc;
|
|
}
|
|
if (*fmt == '.')
|
|
goto hasprec;
|
|
break;
|
|
case '1': case '2': case '3':
|
|
case '4': case '5': case '6':
|
|
case '7': case '8': case '9':
|
|
ww = *fmt++ - '0';
|
|
//flags |= FLAG_WIDTH;
|
|
while (*fmt >= '0' && *fmt <= '9') {
|
|
unsigned digit = *fmt++ - '0';
|
|
if (ww >= UINT_MAX / 10 &&
|
|
(ww > UINT_MAX / 10 || digit > UINT_MAX % 10))
|
|
ww = UINT_MAX;
|
|
else
|
|
ww = ww * 10 + digit;
|
|
}
|
|
if (ww <= INT_MAX)
|
|
width = ww;
|
|
if (*fmt == '.')
|
|
goto hasprec;
|
|
break;
|
|
case '.':
|
|
hasprec:
|
|
fmt++;
|
|
if (*fmt == '*') {
|
|
fmt++;
|
|
wc = va_arg(ap, int);
|
|
if (wc >= 0) {
|
|
flags |= FLAG_PREC;
|
|
prec = wc;
|
|
}
|
|
} else {
|
|
ww = 0;
|
|
while (*fmt >= '0' && *fmt <= '9') {
|
|
unsigned digit = *fmt++ - '0';
|
|
if (ww >= UINT_MAX / 10 &&
|
|
(ww > UINT_MAX / 10 || digit > UINT_MAX % 10))
|
|
ww = UINT_MAX;
|
|
else
|
|
ww = ww * 10 + digit;
|
|
}
|
|
if (ww <= INT_MAX) {
|
|
flags |= FLAG_PREC;
|
|
prec = ww;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
switch (lc = *fmt++) {
|
|
case 'j':
|
|
length = sizeof(intmax_t) * CHAR_BIT;
|
|
break;
|
|
case 'z':
|
|
length = sizeof(size_t) * CHAR_BIT;
|
|
break;
|
|
case 't':
|
|
length = sizeof(ptrdiff_t) * CHAR_BIT;
|
|
break;
|
|
case 'l':
|
|
length = sizeof(unsigned long) * CHAR_BIT;
|
|
if (*fmt == 'l') {
|
|
fmt++;
|
|
length = sizeof(unsigned long long) * CHAR_BIT;
|
|
}
|
|
break;
|
|
case 'h':
|
|
length = sizeof(unsigned short) * CHAR_BIT;
|
|
if (*fmt == 'h') {
|
|
fmt++;
|
|
length = sizeof(unsigned char) * CHAR_BIT;
|
|
}
|
|
break;
|
|
case 'w':
|
|
if (!(*fmt >= '1' && *fmt <= '9'))
|
|
goto error;
|
|
length = *fmt++ - '0';
|
|
if (*fmt >= '0' && *fmt <= '9')
|
|
length = length * 10 + *fmt++ - '0';
|
|
if (length > 64)
|
|
goto error;
|
|
break;
|
|
default:
|
|
fmt--;
|
|
break;
|
|
}
|
|
digits = digits36;
|
|
switch (cc = *fmt++) {
|
|
case 's':
|
|
if (lc == 'l') {
|
|
const wchar_t *wstr = va_arg(ap, const wchar_t *);
|
|
if (!wstr)
|
|
wstr = L"(null)";
|
|
pos += js_format_wstr(fcp, flags, width, prec,
|
|
buf, sizeof buf, wstr);
|
|
} else {
|
|
str = va_arg(ap, const char *);
|
|
if (!str)
|
|
str = "(null)";
|
|
pos += js_format_str(fcp, flags, width, prec, str);
|
|
}
|
|
str = fmt;
|
|
continue;
|
|
case 'c':
|
|
flags &= ~FLAG_ZERO;
|
|
wc = va_arg(ap, int);
|
|
if (lc == 'l') {
|
|
slen = 0;
|
|
if (wc)
|
|
slen = utf8_encode((uint8_t *)buf, wc);
|
|
} else {
|
|
*buf = (char)wc;
|
|
slen = 1;
|
|
}
|
|
break;
|
|
case 'p':
|
|
#ifndef TEST_QUICKJS
|
|
if (*fmt == 's' && fcp->rt) {
|
|
// %ps -> JSString converted to quoted string
|
|
// TODO(chqrlie) allocate buffer if conversion does not fit
|
|
JSString *pstr = va_arg(ap, void *);
|
|
JS_FormatString(fcp->rt, buf, sizeof(buf), pstr, '"');
|
|
pos += js_format_str(fcp, flags, width, prec, buf);
|
|
str = ++fmt;
|
|
continue;
|
|
}
|
|
#endif
|
|
shift = 4;
|
|
prefix[0] = '0';
|
|
prefix[1] = cc = 'x';
|
|
prefix_len = 2;
|
|
length = sizeof(void *) * CHAR_BIT;
|
|
goto radix_number;
|
|
case 'B':
|
|
digits = digits36_upper;
|
|
/* fall thru */
|
|
case 'b':
|
|
shift = 1;
|
|
goto radix_number;
|
|
case 'X':
|
|
digits = digits36_upper;
|
|
/* fall thru */
|
|
case 'x':
|
|
shift = 4;
|
|
goto radix_number;
|
|
case 'o':
|
|
#ifndef TEST_QUICKJS
|
|
if (*fmt == 'a' && length == sizeof(JSAtom) * CHAR_BIT && fcp->rt) {
|
|
// %oa -> JSAtom converted to utf-8 string
|
|
// %#oa -> JSAtom converted to identifier, number or quoted string
|
|
// TODO(chqrlie) allocate buffer if conversion does not fit
|
|
JSAtom atom = va_arg(ap, unsigned);
|
|
JS_FormatAtom(fcp->rt, buf, sizeof(buf), atom, flags & FLAG_HASH);
|
|
pos += js_format_str(fcp, flags, width, prec, buf);
|
|
str = ++fmt;
|
|
continue;
|
|
}
|
|
#endif
|
|
shift = 3;
|
|
radix_number:
|
|
uval = (length <= 32) ? va_arg(ap, uint32_t) : va_arg(ap, uint64_t);
|
|
signbit = (uint64_t)1 << (length - 1);
|
|
/* mask off extra bits, keep value bits */
|
|
uval &= (signbit << 1) - 1;
|
|
slen = uval ? (64 - clz64(uval) + shift - 1) / shift : 1;
|
|
mask = (1ULL << shift) - 1;
|
|
for (wc = slen * shift, i = 0; wc > 0; i++) {
|
|
wc -= shift;
|
|
buf[i] = digits[(uval >> wc) & mask];
|
|
}
|
|
if (flags & FLAG_PREC) {
|
|
if (prec == 0 && uval == 0)
|
|
slen = 0;
|
|
if (slen < prec)
|
|
leading_zeroes = prec - slen;
|
|
flags &= ~FLAG_ZERO;
|
|
}
|
|
if (flags & FLAG_HASH) {
|
|
if (shift == 3) {
|
|
/* at least one leading `0` */
|
|
if (leading_zeroes == 0 && (uval != 0 || slen == 0))
|
|
leading_zeroes = 1;
|
|
} else {
|
|
if (uval) {
|
|
/* output a 0x/0X or 0b/0B prefix */
|
|
prefix[0] = '0';
|
|
prefix[1] = cc;
|
|
prefix_len = 2;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'u':
|
|
case 'd':
|
|
case 'i':
|
|
uval = (length <= 32) ? va_arg(ap, uint32_t) : va_arg(ap, uint64_t);
|
|
signbit = 1ULL << (length - 1);
|
|
if (cc != 'u') {
|
|
if (uval & signbit) {
|
|
prefix[0] = '-';
|
|
uval = -uval;
|
|
}
|
|
prefix_len = (prefix[0] != '\0');
|
|
}
|
|
/* mask off extra bits, keep value bits */
|
|
uval &= (signbit << 1) - 1;
|
|
slen = u64toa(buf, uval);
|
|
if (flags & FLAG_PREC) {
|
|
if (prec == 0 && uval == 0)
|
|
slen = 0;
|
|
if (slen < prec)
|
|
leading_zeroes = prec - slen;
|
|
flags &= ~FLAG_ZERO;
|
|
}
|
|
break;
|
|
case 'A':
|
|
case 'E':
|
|
case 'F':
|
|
case 'G':
|
|
cc += 'a' - 'A';
|
|
digits = digits36_upper;
|
|
/* fall through */
|
|
case 'a':
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
val = va_arg(ap, double);
|
|
fflags = FLAG_ROUND_HALF_EVEN;
|
|
fprec = -1;
|
|
if (flags & FLAG_PREC)
|
|
fprec = prec;
|
|
#define LETTER(c) digits[(c) - 'a' + 10]
|
|
|
|
if (signbit(val)) {
|
|
prefix[0] = '-';
|
|
//val = -val;
|
|
}
|
|
prefix_len = (prefix[0] != '\0');
|
|
if (!isfinite(val)) {
|
|
flags &= ~FLAG_ZERO;
|
|
slen = 3;
|
|
buf[0] = LETTER('i');
|
|
buf[1] = LETTER('n');
|
|
buf[2] = LETTER('f');
|
|
if (isnan(val)) {
|
|
prefix_len = 0;
|
|
buf[0] = LETTER('n');
|
|
buf[1] = LETTER('a');
|
|
buf[2] = LETTER('n');
|
|
}
|
|
break;
|
|
}
|
|
if (flags & FLAG_HASH)
|
|
fflags |= FLAG_FORCE_DOT;
|
|
if (cc == 'a') {
|
|
prefix[prefix_len++] = '0';
|
|
prefix[prefix_len++] = LETTER('x');
|
|
slen = js_format_a(val, buf, fprec, digits, fflags, &trailing_zeroes, &exp);
|
|
suffix_len = js_format_exp(suffix, exp, LETTER('p'), 1);
|
|
break;
|
|
}
|
|
if (fprec < 0)
|
|
fprec = 6;
|
|
if (cc == 'g') {
|
|
fprec -= (fprec != 0);
|
|
if (!(flags & FLAG_HASH))
|
|
fflags |= FLAG_STRIP_ZEROES;
|
|
if (val != 0) {
|
|
exp = (int)+floor(log10(fabs(val)));
|
|
if (fprec <= exp || exp < -4) {
|
|
/* convert with exponent, then re-test border cases */
|
|
// TODO(chqrlie): avoid computing digits twice
|
|
slen = js_format_f(val, buf, fprec, TRUE, fflags,
|
|
&trailing_zeroes, &exp);
|
|
if (fprec < exp || exp < -4) {
|
|
suffix_len = js_format_exp(suffix, exp, LETTER('e'), 2);
|
|
break;
|
|
}
|
|
}
|
|
fprec -= exp;
|
|
}
|
|
}
|
|
slen = js_format_f(val, buf, fprec, cc == 'e', fflags,
|
|
&trailing_zeroes, &exp);
|
|
if (cc == 'e') {
|
|
suffix_len = js_format_exp(suffix, exp, LETTER('e'), 2);
|
|
}
|
|
break;
|
|
case '\0':
|
|
fmt--;
|
|
continue;
|
|
error:
|
|
default:
|
|
/* invalid format: stop conversions and print format string */
|
|
fmt += strlen(fmt);
|
|
continue;
|
|
}
|
|
pad = 0;
|
|
// XXX: potential overflow
|
|
wc = prefix_len + leading_zeroes + slen + trailing_zeroes + suffix_len;
|
|
if (width > wc) {
|
|
pad = width - wc;
|
|
if (!(flags & FLAG_LEFT)) {
|
|
if (flags & FLAG_ZERO) {
|
|
/* left pad with zeroes between prefix and string */
|
|
leading_zeroes += pad;
|
|
pad = 0;
|
|
} else {
|
|
/* left pad with spaces */
|
|
pos += js_format_spaces(fcp, pad);
|
|
pad = 0;
|
|
}
|
|
}
|
|
}
|
|
/* output prefix: sign and/or 0x/0b */
|
|
if (prefix_len)
|
|
pos += fcp->write(fcp, prefix, prefix_len);
|
|
if (leading_zeroes)
|
|
pos += js_format_zeroes(fcp, leading_zeroes);
|
|
/* output string fragment */
|
|
pos += fcp->write(fcp, buf, slen);
|
|
if (trailing_zeroes)
|
|
pos += js_format_zeroes(fcp, trailing_zeroes);
|
|
/* output suffix: exponent */
|
|
if (suffix_len)
|
|
pos += fcp->write(fcp, suffix, suffix_len);
|
|
/* right pad with spaces */
|
|
if (pad)
|
|
pos += js_format_spaces(fcp, pad);
|
|
str = fmt;
|
|
}
|
|
return (int)pos;
|
|
}
|
|
|
|
static int js_snprintf_write(JSFormatContext *fcp, const char *str, size_t len)
|
|
{
|
|
size_t i, pos = fcp->pos;
|
|
char *dest = fcp->ptr;
|
|
for (i = 0; i < len; i++) {
|
|
if (pos < fcp->size)
|
|
dest[pos] = str[i];
|
|
pos++;
|
|
}
|
|
fcp->pos = pos;
|
|
return len;
|
|
}
|
|
|
|
int js_snprintf(JSContext *ctx, char *dest, size_t size, const char *fmt, ...)
|
|
{
|
|
JSFormatContext fc = { JS_GET_CTX_RT(ctx), dest, size, 0, js_snprintf_write };
|
|
va_list ap;
|
|
int len;
|
|
|
|
va_start(ap, fmt);
|
|
len = js_format(&fc, fmt, ap);
|
|
va_end(ap);
|
|
if (fc.pos < fc.size)
|
|
dest[fc.pos] = '\0';
|
|
else if (fc.size > 0)
|
|
dest[fc.size - 1] = '\0';
|
|
return len;
|
|
}
|
|
|
|
int js_vsnprintf(JSContext *ctx, char *dest, size_t size, const char *fmt, va_list ap)
|
|
{
|
|
JSFormatContext fc = { JS_GET_CTX_RT(ctx), dest, size, 0, js_snprintf_write };
|
|
int len;
|
|
|
|
len = js_format(&fc, fmt, ap);
|
|
if (fc.pos < fc.size)
|
|
dest[fc.pos] = '\0';
|
|
else if (fc.size > 0)
|
|
dest[fc.size - 1] = '\0';
|
|
return len;
|
|
}
|
|
|
|
static int dbuf_printf_write(JSFormatContext *fcp, const char *str, size_t len)
|
|
{
|
|
return dbuf_put(fcp->ptr, str, len);
|
|
}
|
|
|
|
int dbuf_vprintf_ext(DynBuf *s, const char *fmt, va_list ap)
|
|
{
|
|
JSFormatContext fc = { JS_GET_DBUF_RT(s), s, 0, 0, dbuf_printf_write };
|
|
return js_format(&fc, fmt, ap);
|
|
}
|
|
|
|
static int js_fprintf_write(JSFormatContext *fcp, const char *str, size_t len)
|
|
{
|
|
return fwrite(str, 1, len, fcp->ptr);
|
|
}
|
|
|
|
int js_printf(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
JSFormatContext fc = { JS_GET_CTX_RT(ctx), stdout, 0, 0, js_fprintf_write };
|
|
va_list ap;
|
|
int len;
|
|
|
|
va_start(ap, fmt);
|
|
len = js_format(&fc, fmt, ap);
|
|
va_end(ap);
|
|
return len;
|
|
}
|
|
|
|
int js_vprintf(JSContext *ctx, const char *fmt, va_list ap)
|
|
{
|
|
JSFormatContext fc = { JS_GET_CTX_RT(ctx), stdout, 0, 0, js_fprintf_write };
|
|
return js_format(&fc, fmt, ap);
|
|
}
|
|
|
|
int js_printf_RT(JSRuntime *rt, const char *fmt, ...)
|
|
{
|
|
JSFormatContext fc = { JS_GET_RT_RT(rt), stdout, 0, 0, js_fprintf_write };
|
|
va_list ap;
|
|
int len;
|
|
|
|
va_start(ap, fmt);
|
|
len = js_format(&fc, fmt, ap);
|
|
va_end(ap);
|
|
return len;
|
|
}
|
|
|
|
int js_vprintf_RT(JSRuntime *rt, const char *fmt, va_list ap)
|
|
{
|
|
JSFormatContext fc = { JS_GET_RT_RT(rt), stdout, 0, 0, js_fprintf_write };
|
|
return js_format(&fc, fmt, ap);
|
|
}
|
|
|
|
int js_fprintf_RT(JSRuntime *rt, FILE *fp, const char *fmt, ...)
|
|
{
|
|
JSFormatContext fc = { JS_GET_RT_RT(rt), fp, 0, 0, js_fprintf_write };
|
|
va_list ap;
|
|
int len;
|
|
|
|
va_start(ap, fmt);
|
|
len = js_format(&fc, fmt, ap);
|
|
va_end(ap);
|
|
return len;
|
|
}
|
|
|
|
int js_vfprintf_RT(JSRuntime *rt, FILE *fp, const char *fmt, va_list ap)
|
|
{
|
|
JSFormatContext fc = { JS_GET_RT_RT(rt), fp, 0, 0, js_fprintf_write };
|
|
return js_format(&fc, fmt, ap);
|
|
}
|
|
|
|
#undef JS_GET_CTX_RT
|
|
#undef JS_GET_RT_RT
|
|
#undef JS_GET_DBUF_RT
|
|
#undef COMP10
|
|
#undef COMP10_LEN
|
|
#undef COMP10_MAX_SHIFT
|