Skip to content

Commit

Permalink
feat: add misc.printf, misc.sprintf
Browse files Browse the repository at this point in the history
  • Loading branch information
XuJiandong committed Jan 16, 2025
1 parent 5d4cbb6 commit 208bad0
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 7 deletions.
207 changes: 201 additions & 6 deletions src/misc_module.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "cutils.h"
#include "misc_module.h"
#define BLAKE2_IMPL_H
Expand Down Expand Up @@ -307,8 +308,204 @@ static JSValue js_throw_exception(JSContext *ctx, JSValueConst this_val, int arg
return error;
}

static const JSCFunctionListEntry js_test_funcs[] = {
static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) {
dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt);
}

static bool my_isdigit(int c) { return (c >= '0' && c <= '9'); }

static JSValue js_printf_internal(JSContext *ctx, int argc, JSValueConst *argv, bool to_console) {
char fmtbuf[32];
uint8_t cbuf[UTF8_CHAR_LEN_MAX + 1];
JSValue res;
DynBuf dbuf;
const char *fmt_str;
const uint8_t *fmt, *fmt_end;
const uint8_t *p;
char *q;
int i, c, len, mod;
size_t fmt_len;
int32_t int32_arg;
int64_t int64_arg;
double double_arg;
const char *string_arg;
/* Use indirect call to dbuf_printf to prevent gcc warning */
int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void *)dbuf_printf;

js_std_dbuf_init(ctx, &dbuf);

if (argc > 0) {
fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]);
if (!fmt_str) goto fail;

i = 1;
fmt = (const uint8_t *)fmt_str;
fmt_end = fmt + fmt_len;
while (fmt < fmt_end) {
for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) continue;
dbuf_put(&dbuf, p, fmt - p);
if (fmt >= fmt_end) break;
q = fmtbuf;
*q++ = *fmt++; /* copy '%' */

/* flags */
for (;;) {
c = *fmt;
if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || c == '\'') {
if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid;
*q++ = c;
fmt++;
} else {
break;
}
}
/* width */
if (*fmt == '*') {
if (i >= argc) goto missing;
if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail;
q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg);
fmt++;
} else {
while (my_isdigit(*fmt)) {
if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid;
*q++ = *fmt++;
}
}
if (*fmt == '.') {
if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid;
*q++ = *fmt++;
if (*fmt == '*') {
if (i >= argc) goto missing;
if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail;
q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg);
fmt++;
} else {
while (my_isdigit(*fmt)) {
if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid;
*q++ = *fmt++;
}
}
}

/* we only support the "l" modifier for 64 bit numbers */
mod = ' ';
if (*fmt == 'l') {
mod = *fmt++;
}

/* type */
c = *fmt++;
if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid;
*q++ = c;
*q = '\0';

switch (c) {
case 'c':
if (i >= argc) goto missing;
if (JS_IsString(argv[i])) {
string_arg = JS_ToCString(ctx, argv[i++]);
if (!string_arg) goto fail;
int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p);
JS_FreeCString(ctx, string_arg);
} else {
if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail;
}
/* handle utf-8 encoding explicitly */
if ((unsigned)int32_arg > 0x10FFFF) int32_arg = 0xFFFD;
/* ignore conversion flags, width and precision */
len = unicode_to_utf8(cbuf, int32_arg);
dbuf_put(&dbuf, cbuf, len);
break;

case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
if (i >= argc) goto missing;
if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) goto fail;
if (mod == 'l') {
/* 64 bit number */
if (q >= fmtbuf + sizeof(fmtbuf) - 2) goto invalid;
q[1] = q[-1];
q[-1] = q[0] = 'l';
q[2] = '\0';
dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg);
} else {
dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg);
}
break;

case 's':
if (i >= argc) goto missing;
/* XXX: handle strings containing null characters */
string_arg = JS_ToCString(ctx, argv[i++]);
if (!string_arg) goto fail;
dbuf_printf_fun(&dbuf, fmtbuf, string_arg);
JS_FreeCString(ctx, string_arg);
break;

case 'e':
case 'f':
case 'g':
case 'a':
case 'E':
case 'F':
case 'G':
case 'A':
if (i >= argc) goto missing;
if (JS_ToFloat64(ctx, &double_arg, argv[i++])) goto fail;
dbuf_printf_fun(&dbuf, fmtbuf, double_arg);
break;

case '%':
dbuf_putc(&dbuf, '%');
break;

default:
/* XXX: should support an extension mechanism */
invalid:
JS_ThrowTypeError(ctx, "invalid conversion specifier in format string");
goto fail;
missing:
JS_ThrowReferenceError(ctx, "missing argument for conversion specifier");
goto fail;
}
}
JS_FreeCString(ctx, fmt_str);
}
if (dbuf.error) {
res = JS_ThrowOutOfMemory(ctx);
} else {
if (to_console) {
dbuf_putc(&dbuf, '\0');
ckb_debug((char *)dbuf.buf);
res = JS_NewInt32(ctx, len);
} else {
res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size);
}
}
dbuf_free(&dbuf);
return res;

fail:
dbuf_free(&dbuf);
return JS_EXCEPTION;
}

static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
return js_printf_internal(ctx, argc, argv, false);
}

static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
return js_printf_internal(ctx, argc, argv, true);
}

static const JSCFunctionListEntry js_misc_funcs[] = {
JS_CFUNC_DEF("throw_exception", 1, js_throw_exception),
JS_CFUNC_DEF("sprintf", 1, js_std_sprintf),
JS_CFUNC_DEF("printf", 1, js_std_printf),
};

static int js_misc_init(JSContext *ctx, JSModuleDef *m) {
Expand Down Expand Up @@ -340,10 +537,8 @@ static int js_misc_init(JSContext *ctx, JSModuleDef *m) {
JS_SetPropertyFunctionList(ctx, base64, js_base64_funcs, countof(js_base64_funcs));
JS_SetModuleExport(ctx, m, "base64", base64);

// Create test object and add functions
JSValue test = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, test, js_test_funcs, countof(js_test_funcs));
JS_SetModuleExport(ctx, m, "test", test);
// functions without submodule
JS_SetModuleExportList(ctx, m, js_misc_funcs, countof(js_misc_funcs));

return 0;
}
Expand All @@ -356,6 +551,6 @@ int js_init_module_misc(JSContext *ctx) {
JS_AddModuleExport(ctx, m, "Smt");
JS_AddModuleExport(ctx, m, "hex");
JS_AddModuleExport(ctx, m, "base64");
JS_AddModuleExport(ctx, m, "test");
JS_AddModuleExportList(ctx, m, js_misc_funcs, countof(js_misc_funcs));
return 0;
}
2 changes: 1 addition & 1 deletion tests/basic/test_exception.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function main_2() {
}
function main_3() {
console.log("main_3() function");
misc.test.throw_exception("Exception in main_3() function");
misc.throw_exception("Exception in main_3() function");
}

function main() {
Expand Down
8 changes: 8 additions & 0 deletions tests/module/test_misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,16 @@ function test_import_meta() {
console.log('test_import_meta ok');
}

function test_printf() {
misc.printf('Hello, World: %d', 100);
const str = misc.sprintf("%s, %d", "Hello, World", 100);
console.assert(str === "Hello, World, 100", 'sprintf failed');
console.log('test_printf ok');
}

// Add the new test cases to the main execution
console.log('test_misc.js ...');
test_printf();
test_ckb_smt_verify1(true);
test_ckb_smt_verify1(false);
test_ckb_smt_verify2(true);
Expand Down

0 comments on commit 208bad0

Please sign in to comment.