diff --git a/meson.build b/meson.build index e5b7ae56..1fe25d95 100644 --- a/meson.build +++ b/meson.build @@ -295,7 +295,7 @@ src += [ 'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', 'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c', - 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c' + 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c' ] # quirc src src += [ diff --git a/scripts/engine.cm b/scripts/engine.cm index 95e2e286..21900c4e 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -66,7 +66,7 @@ function noop() {} globalThis.log = new Proxy(logs, { get(target,prop,receiver) { if (target[prop]) - return (...args) => args.forEach(arg => target[prop](arg)) + return (...args) => target[prop](args.join(' ')) return noop } @@ -254,7 +254,8 @@ var default_config = { ar_timer: 60, actor_memory:0, net_service:0.1, - reply_timeout:60 + reply_timeout:60, + main: false, } config.system ??= {} @@ -275,11 +276,8 @@ function load_actor_config(program) { } if (config.actors && config.actors[actor_name]) { - // Merge actor config into cell.args - for (var key in config.actors[actor_name]) { - log.console(`setting ${key}`) + for (var key in config.actors[actor_name]) cell.args[key] = config.actors[actor_name][key] - } } } @@ -557,6 +555,8 @@ function actor_send_immediate(actor, send) { } } +var jswota = use('jswota') + function actor_send(actor, message) { if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop return @@ -573,7 +573,14 @@ function actor_send(actor, message) { // message to actor in same flock if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) { - actor_mod.mailbox_push(actor[ACTORDATA].id, message) + var st = time.number() + var m1 = jswota.encode(message) + var m1t = time.number()-st + st = time.number() + var m2 = wota.encode(message) + var m2t = time.number()-st + log.console(`jswota: ${m1.length} bits in ${m1t}. wota: ${m2.length} bits in ${m2t}.`) + actor_mod.mailbox_push(actor[ACTORDATA].id, m2) return } diff --git a/scripts/jswota.cm b/scripts/jswota.cm new file mode 100644 index 00000000..4cd04943 --- /dev/null +++ b/scripts/jswota.cm @@ -0,0 +1,134 @@ +var blob = use('blob') +var utf8 = use('utf8') + +var INT = new blob(8, false) +stone(INT) + +var FP = new blob(8) +FP.write_fit(1,8) +stone(FP) + +var ARRAY = new blob(8) +ARRAY.write_fit(2,8) +stone(ARRAY) + +var RECORD = new blob(8) +RECORD.write_fit(3,8) +stone(RECORD) + +var BLOB = new blob(8) +BLOB.write_fit(4,8) +stone(BLOB) + +var TEXT = new blob(8) +TEXT.write_fit(5,8) +stone(TEXT) + +var SYMBOL = new blob(8) +SYMBOL.write_fit(7,8) +stone(SYMBOL) + +var NULL = new blob(56) +NULL.write_fit(0,56) +stone(NULL) + +var FALSE = new blob(56) +FALSE.write_fit(2,56) +stone(FALSE) + +var TRUE = new blob(56) +TRUE.write_fit(3, 56) +stone(TRUE) + +var PRIVATE = new blob(56) +PRIVATE.write_fit(8, 56) +stone(PRIVATE) + +var SYSTEM = new blob(56) +SYSTEM.write_fit(9, 56) +stone(SYSTEM) + +var encoders = {} + +encoders.number = function(b, val) +{ + // encoding all as floats + b.write_fit(0,56) + b.write_blob(FP) + b.write_number(val) +} + +function encode_array(b, val) +{ + b.write_fit(val.length, 56) + b.write_blob(ARRAY) + for (var v of val) + encode_val(b, v) +} + +function encode_object(b, val) +{ + var keys = Object.keys(val) + b.write_fit(b, keys.length) + b.write_blob(RECORD) + for (var key of keys) { + if (typeof val[key] === 'function') continue + encoders.string(b, key) + encode_val(b, val[key]) + } +} + +function encode_blob(b, val) +{ + b.write_fit(val.length, 56) + b.write_blob(BLOB) + b.write_blob(val) +} + +encoders.object = function(b, val) +{ + if (Array.isArray(val)) + encode_array(b,val) + else if (val instanceof blob) + encode_blob(b,val) + else + encode_object(b,val) +} + +encoders.string = function(b, val) +{ + // encoding as utf8 + b.write_fit(utf8.byte_length(val), 56) + b.write_blob(TEXT) + b.write_blob(utf8.encode(val)) +} + +encoders.boolean = function(b, val) +{ + if (val) + b.write_blob(TRUE) + else + b.write_blob(FALSE) + b.write_blob(SYMBOL) +} + +encoders.undefined = function(b, val) +{ + b.write_blob(NULL) + b.write_blob(SYMBOL) +} + +function encode_val(b, val) +{ + encoders[typeof val](b, val) +} + +function encode(val) +{ + var b = new blob(8*256)// guess a good length + encode_val(b,val) + + return stone(b) +} + +return { INT,FP,ARRAY,RECORD,BLOB,TEXT,SYMBOL, encode } \ No newline at end of file diff --git a/source/blob.h b/source/blob.h index 3aaefd3b..91d760de 100644 --- a/source/blob.h +++ b/source/blob.h @@ -46,6 +46,7 @@ int blob_write_bit(blob *b, int bit_val); int blob_write_blob(blob *b, const blob *src); int blob_write_dec64(blob *b, double d); int blob_write_int64(blob *b, int64_t i); +int blob_write_fit(blob *b, int64_t value, int len); int blob_write_pad(blob *b, int block_size); int blob_write_text(blob *b, const char *text); @@ -54,6 +55,7 @@ int blob_read_bit(const blob *b, size_t pos, int *out_bit); blob *blob_read_blob(const blob *b, size_t from, size_t to); int blob_read_dec64(const blob *b, size_t from, double *out_value); int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value); +int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value); int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read); int blob_pad_check(const blob *b, size_t from, int block_size); @@ -315,6 +317,23 @@ int blob_write_int64(blob *b, int64_t i) { return 0; } +int blob_write_fit(blob *b, int64_t value, int len) { + if (!b || b->is_stone) return -1; + if (len < 1 || len > 64) return -1; + + // Check if value fits in len bits with sign + if (len < 64) { + int64_t max = (1LL << (len - 1)) - 1; + int64_t min = -(1LL << (len - 1)); + if (value < min || value > max) return -1; + } + + if (blob_ensure_capacity(b, len) < 0) return -1; + copy_bits_fast(&value, b->data, 0, len - 1, (int)b->length); + b->length += len; + return 0; +} + int blob_write_pad(blob *b, int block_size) { if (!b || b->is_stone) return -1; if (block_size <= 0) return -1; @@ -380,6 +399,24 @@ int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value) return 0; } +int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value) { + if (!b || !b->is_stone || !out_value) return -1; + if (len < 1 || len > 64) return -1; + if (from + (size_t)len > b->length) return -1; + + *out_value = 0; + copy_bits_fast(b->data, out_value, (int)from, (int)(from + len - 1), 0); + + // Sign extend if necessary (if the high bit is set and len < 64) + if (len < 64 && (*out_value & (1LL << (len - 1)))) { + // Set all bits above len to 1 for negative numbers + int64_t mask = ~((1LL << len) - 1); + *out_value |= mask; + } + + return 0; +} + int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) { if (!b || !b->is_stone || !out_text || !bits_read) return -1; // Need at least 64 bits for length prefix diff --git a/source/jsffi.c b/source/jsffi.c index 40984898..334e788e 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -55,6 +55,7 @@ #include "qjs_sdl.h" #include "qjs_kim.h" #include "qjs_utf8.h" +#include "qjs_fit.h" #ifndef NSTEAM #include "qjs_steam.h" #endif @@ -1558,6 +1559,7 @@ void ffi_load(JSContext *js) arrput(rt->module_registry, MISTLINE(miniz)); arrput(rt->module_registry, MISTLINE(kim)); arrput(rt->module_registry, MISTLINE(utf8)); + arrput(rt->module_registry, MISTLINE(fit)); // power user arrput(rt->module_registry, MISTLINE(js)); diff --git a/source/qjs_actor.c b/source/qjs_actor.c index 27f931f2..da9f6809 100644 --- a/source/qjs_actor.c +++ b/source/qjs_actor.c @@ -78,13 +78,13 @@ JSC_CCALL(os_createactor, if (rt->disrupt) return JS_ThrowInternalError(js, "Can't start a new actor while disrupting."); - void *startup = value2wota(js, argv[0], JS_UNDEFINED); + void *startup = value2wota(js, argv[0], JS_UNDEFINED, NULL); create_actor(startup); ) JSC_CCALL(os_mailbox_push, if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and a message"); - if (!JS_IsObject(argv[1])) return JS_ThrowInternalError(js, "Object to push must be an object."); + if (!js_is_blob(js, argv[1])) return JS_ThrowInternalError(js, "Can only send blobs."); const char *id = JS_ToCString(js, argv[0]); int exist = actor_exists(id); @@ -97,7 +97,7 @@ JSC_CCALL(os_mailbox_push, cell_rt *target = get_actor(id); JS_FreeCString(js,id); - JSValue sys = JS_GetPropertyStr(js, argv[1], "__SYSTEM__"); +/* JSValue sys = JS_GetPropertyStr(js, argv[1], "__SYSTEM__"); if (!JS_IsUndefined(sys)) { const char *k = JS_ToCString(js,sys); int stop = 0; @@ -110,10 +110,15 @@ JSC_CCALL(os_mailbox_push, if (stop) return JS_UNDEFINED; } - - void *data = value2wota(js, argv[1], JS_UNDEFINED); +*/ +// void *data = value2wota(js, argv[1], JS_UNDEFINED, NULL); + size_t size; + void *data = js_get_blob_data(js, &size, argv[1]); - const char *err = send_message(id, data); + void *copy = malloc(size); + memcpy(copy, data, size); + + const char *err = send_message(id, copy); if (err) { free(data); return JS_ThrowInternalError(js, "Could not send message: %s", err); diff --git a/source/qjs_blob.c b/source/qjs_blob.c index 324b1cf1..d8334261 100644 --- a/source/qjs_blob.c +++ b/source/qjs_blob.c @@ -186,6 +186,29 @@ static JSValue js_blob_write_number(JSContext *ctx, JSValueConst this_val, return JS_UNDEFINED; } +// blob.write_fit(value, len) +static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + if (argc < 2) + return JS_ThrowTypeError(ctx, "write_fit(value, len) requires 2 arguments"); + + blob *bd = js2blob(ctx, this_val); + if (!bd) + return JS_ThrowTypeError(ctx, "write_fit: not called on a blob"); + + int64_t value; + int32_t len; + + if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION; + + if (blob_write_fit(bd, value, len) < 0) { + return JS_ThrowTypeError(ctx, "write_fit: value doesn't fit in specified bits, stone blob, or OOM"); + } + + return JS_UNDEFINED; +} + // blob.write_kim(fit) static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -311,6 +334,40 @@ static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val, return JS_NewFloat64(ctx, d); } +// blob.read_fit(from, len) +static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + if (argc < 2) { + return JS_ThrowTypeError(ctx, "read_fit(from, len) requires 2 arguments"); + } + + blob *bd = js2blob(ctx, this_val); + if (!bd) { + return JS_ThrowTypeError(ctx, "read_fit: not called on a blob"); + } + + if (!bd->is_stone) { + return JS_ThrowTypeError(ctx, "read_fit: blob must be stone"); + } + + int64_t from; + int32_t len; + + if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION; + + if (from < 0) { + return JS_ThrowRangeError(ctx, "read_fit: position must be non-negative"); + } + + int64_t value; + if (blob_read_fit(bd, from, len, &value) < 0) { + return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length"); + } + + return JS_NewInt64(ctx, value); +} + // blob.read_text(from) static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -402,6 +459,7 @@ static const JSCFunctionListEntry js_blob_funcs[] = { JS_CFUNC_DEF("write_bit", 1, js_blob_write_bit), JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob), JS_CFUNC_DEF("write_number", 1, js_blob_write_number), + JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit), JS_CFUNC_DEF("write_text", 1, js_blob_write_text), JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad), @@ -409,6 +467,7 @@ static const JSCFunctionListEntry js_blob_funcs[] = { JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical), JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob), JS_CFUNC_DEF("read_number", 1, js_blob_read_number), + JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit), JS_CFUNC_DEF("read_text", 1, js_blob_read_text), JS_CFUNC_DEF("pad?", 2, js_blob_pad_q), diff --git a/source/qjs_fit.c b/source/qjs_fit.c new file mode 100644 index 00000000..f921327e --- /dev/null +++ b/source/qjs_fit.c @@ -0,0 +1,257 @@ +#include "qjs_fit.h" +#include "jsffi.h" +#include +#include + +#define FIT_BITS 56 +#define FIT_MAX ((1LL << 55) - 1) +#define FIT_MIN (-(1LL << 55)) +#define FIT_MASK ((1ULL << 56) - 1) + +static int is_fit(int64_t n) { + return n >= FIT_MIN && n <= FIT_MAX; +} + +static int64_t to_fit_int(JSContext *js, JSValue val) { + if (!JS_IsNumber(val)) return LLONG_MAX; + + int64_t n; + if (JS_ToInt64(js, &n, val) < 0) return LLONG_MAX; + + if (!is_fit(n)) return LLONG_MAX; + + return n; +} + +static JSValue fit_result(JSContext *js, int64_t result) { + if (!is_fit(result)) return JS_NULL; + return JS_NewInt64(js, result); +} + +JSC_CCALL(fit_and, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + + int64_t result = first & second; + return fit_result(js, result); +) + +JSC_CCALL(fit_left, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + if (second < 0 || second >= FIT_BITS) return JS_NULL; + + // Mask to 56 bits then sign extend + uint64_t unsigned_first = (uint64_t)first & FIT_MASK; + uint64_t result = (unsigned_first << second) & FIT_MASK; + + // Sign extend from bit 55 + if (result & (1ULL << 55)) { + result |= ~FIT_MASK; + } + + return JS_NewInt64(js, (int64_t)result); +) + +JSC_CCALL(fit_mask, + if (!JS_IsNumber(argv[0])) return JS_NULL; + + int32_t n; + if (JS_ToInt32(js, &n, argv[0]) < 0) return JS_NULL; + + if (n > FIT_BITS || n < -FIT_BITS) return JS_NULL; + + if (n == 0) return JS_NewInt64(js, 0); + + int64_t result; + if (n > 0) { + // Create n ones + if (n == FIT_BITS) { + result = -1; + } else { + result = (1LL << n) - 1; + } + } else { + // Create -n zeros (which is 56+n ones) + int ones = FIT_BITS + n; + if (ones == 0) { + result = 0; + } else if (ones == FIT_BITS) { + result = -1; + } else { + uint64_t mask = (1ULL << ones) - 1; + result = ~mask; + // Ensure result is within 56-bit range + result = (int64_t)((uint64_t)result & FIT_MASK); + // Sign extend + if (result & (1LL << 55)) { + result |= ~FIT_MASK; + } + } + } + + return JS_NewInt64(js, result); +) + +JSC_CCALL(fit_not, + int64_t val = to_fit_int(js, argv[0]); + if (val == LLONG_MAX) return JS_NULL; + + uint64_t result = ~(uint64_t)val & FIT_MASK; + + // Sign extend + if (result & (1ULL << 55)) { + result |= ~FIT_MASK; + } + + return JS_NewInt64(js, (int64_t)result); +) + +JSC_CCALL(fit_ones, + int64_t val = to_fit_int(js, argv[0]); + if (val == LLONG_MAX) return JS_NULL; + + uint64_t uval = (uint64_t)val & FIT_MASK; + int count = 0; + + for (int i = 0; i < FIT_BITS; i++) { + if (uval & (1ULL << i)) count++; + } + + return JS_NewInt32(js, count); +) + +JSC_CCALL(fit_or, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + + int64_t result = first | second; + return fit_result(js, result); +) + +JSC_CCALL(fit_reverse, + int64_t val = to_fit_int(js, argv[0]); + if (val == LLONG_MAX) return JS_NULL; + + uint64_t uval = (uint64_t)val & FIT_MASK; + uint64_t result = 0; + + for (int i = 0; i < FIT_BITS; i++) { + if (uval & (1ULL << i)) { + result |= (1ULL << (FIT_BITS - 1 - i)); + } + } + + // Sign extend + if (result & (1ULL << 55)) { + result |= ~FIT_MASK; + } + + return JS_NewInt64(js, (int64_t)result); +) + +JSC_CCALL(fit_right, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + if (second < 0 || second >= FIT_BITS) return JS_NULL; + + // Zero fill right shift + uint64_t ufirst = (uint64_t)first & FIT_MASK; + uint64_t result = ufirst >> second; + + return JS_NewInt64(js, (int64_t)result); +) + +JSC_CCALL(fit_right_signed, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + if (second < 0 || second >= FIT_BITS) return JS_NULL; + + // Sign extend to 64 bits for arithmetic shift + int64_t extended = first; + if (first & (1LL << 55)) { + extended |= ~FIT_MASK; + } + + int64_t result = extended >> second; + + // Ensure result is within fit range + return fit_result(js, result); +) + +JSC_CCALL(fit_rotate, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + + // Normalize rotation amount + int rotation = ((int)second % FIT_BITS + FIT_BITS) % FIT_BITS; + + uint64_t ufirst = (uint64_t)first & FIT_MASK; + uint64_t result = ((ufirst << rotation) | (ufirst >> (FIT_BITS - rotation))) & FIT_MASK; + + // Sign extend + if (result & (1ULL << 55)) { + result |= ~FIT_MASK; + } + + return JS_NewInt64(js, (int64_t)result); +) + +JSC_CCALL(fit_xor, + int64_t first = to_fit_int(js, argv[0]); + int64_t second = to_fit_int(js, argv[1]); + + if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL; + + int64_t result = first ^ second; + return fit_result(js, result); +) + +JSC_CCALL(fit_zeros, + int64_t val = to_fit_int(js, argv[0]); + if (val == LLONG_MAX) return JS_NULL; + + uint64_t uval = (uint64_t)val & FIT_MASK; + + int count = 0; + for (int i = FIT_BITS - 1; i >= 0; i--) { + if (uval & (1ULL << i)) break; + count++; + } + + return JS_NewInt32(js, count); +) + +static const JSCFunctionListEntry js_fit_funcs[] = { + MIST_FUNC_DEF(fit, and, 2), + MIST_FUNC_DEF(fit, left, 2), + MIST_FUNC_DEF(fit, mask, 1), + MIST_FUNC_DEF(fit, not, 1), + MIST_FUNC_DEF(fit, ones, 1), + MIST_FUNC_DEF(fit, or, 2), + MIST_FUNC_DEF(fit, reverse, 1), + MIST_FUNC_DEF(fit, right, 2), + MIST_FUNC_DEF(fit, right_signed, 2), + MIST_FUNC_DEF(fit, rotate, 2), + MIST_FUNC_DEF(fit, xor, 2), + MIST_FUNC_DEF(fit, zeros, 1), +}; + +JSValue js_fit_use(JSContext *js) +{ + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs)); + return mod; +} \ No newline at end of file diff --git a/source/qjs_fit.h b/source/qjs_fit.h new file mode 100644 index 00000000..89eaa238 --- /dev/null +++ b/source/qjs_fit.h @@ -0,0 +1,8 @@ +#ifndef QJS_FIT_H +#define QJS_FIT_H + +#include + +JSValue js_fit_use(JSContext *js); + +#endif \ No newline at end of file diff --git a/source/qjs_nota.c b/source/qjs_nota.c index 5f853a73..898e7220 100755 --- a/source/qjs_nota.c +++ b/source/qjs_nota.c @@ -1,7 +1,6 @@ #include "qjs_nota.h" #include "qjs_blob.h" -#define KIM_IMPLEMENTATION #define NOTA_IMPLEMENTATION #include "nota.h" diff --git a/source/qjs_wota.c b/source/qjs_wota.c index 109d5f35..eaccaf2e 100644 --- a/source/qjs_wota.c +++ b/source/qjs_wota.c @@ -287,7 +287,7 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val, return data_ptr; } -void *value2wota(JSContext *ctx, JSValue v, JSValue replacer) +void *value2wota(JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) { WotaEncodeContext enc_s, *enc = &enc_s; @@ -305,6 +305,7 @@ void *value2wota(JSContext *ctx, JSValue v, JSValue replacer) JS_FreeValue(ctx, enc->visited_stack); size_t total_bytes = enc->wb.size * sizeof(uint64_t); void *wota = realloc(enc->wb.data, total_bytes); + if (bytes) *bytes = total_bytes; return wota; } @@ -320,22 +321,10 @@ JSValue wota2value(JSContext *ctx, void *wota) static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument"); - WotaEncodeContext enc_s, *enc = &enc_s; - enc->ctx = ctx; - enc->visited_stack = JS_NewArray(ctx); - enc->cycle = 0; - enc->replacer = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_UNDEFINED; - wota_buffer_init(&enc->wb, 16); - wota_encode_value(enc, argv[0], JS_UNDEFINED, JS_UNDEFINED); - if (enc->cycle) { - JS_FreeValue(ctx, enc->visited_stack); - wota_buffer_free(&enc->wb); - return JS_ThrowReferenceError(ctx, "Cannot encode cyclic object with wota"); - } - JS_FreeValue(ctx, enc->visited_stack); - size_t total_bytes = enc->wb.size * sizeof(uint64_t); - JSValue ret = js_new_blob_stoned_copy(ctx, (uint8_t *)enc->wb.data, total_bytes); - wota_buffer_free(&enc->wb); + size_t total_bytes; + void *wota = value2wota(ctx, argv[0], JS_IsFunction(ctx,argv[1]) ? argv[1] : JS_UNDEFINED, &total_bytes); + JSValue ret = js_new_blob_stoned_copy(ctx, wota, total_bytes); + free(wota); return ret; } diff --git a/source/qjs_wota.h b/source/qjs_wota.h index 6e0ef707..cf8a6710 100644 --- a/source/qjs_wota.h +++ b/source/qjs_wota.h @@ -6,7 +6,7 @@ JSValue js_wota_use(JSContext*); -void *value2wota(JSContext*, JSValue, JSValue); +void *value2wota(JSContext*, JSValue val, JSValue replacer, size_t *bytes); JSValue wota2value(JSContext*, void*); #endif diff --git a/tests/fit.ce b/tests/fit.ce new file mode 100644 index 00000000..dd70e5df --- /dev/null +++ b/tests/fit.ce @@ -0,0 +1,122 @@ +var fit = use("fit"); + +var tests_run = 0; +var tests_passed = 0; +var tests_failed = 0; + +function test(description, actual, expected) { + tests_run++; + if (actual === expected) { + tests_passed++; + log.console("✓", description, "=", actual); + } else { + tests_failed++; + log.console("✗", description, "expected", expected, "but got", actual); + } +} + +log.console("Running fit module tests...\n"); + +// Test fit.and +test("fit.and(12, 10)", fit.and(12, 10), 8); +test("fit.and(16, 2)", fit.and(16, 2), 0); +test("fit.and(15, 3)", fit.and(15, 3), 3); +test("fit.and(13, 3)", fit.and(13, 3), 1); +test("fit.and('10', 3)", fit.and("10", 3), null); + +// Test fit.or +test("fit.or(12, 10)", fit.or(12, 10), 14); +test("fit.or(16, 2)", fit.or(16, 2), 18); +test("fit.or(15, 3)", fit.or(15, 3), 15); +test("fit.or(13, 3)", fit.or(13, 3), 15); + +// Test fit.xor +test("fit.xor(12, 10)", fit.xor(12, 10), 6); +test("fit.xor(16, 2)", fit.xor(16, 2), 18); +test("fit.xor(15, 3)", fit.xor(15, 3), 12); +test("fit.xor(13, 3)", fit.xor(13, 3), 14); +test("fit.xor(13.01, 3)", fit.xor(13.01, 3), null); + +// Test fit.left +test("fit.left(12, 10)", fit.left(12, 10), 12288); +test("fit.left(16, 2)", fit.left(16, 2), 64); +test("fit.left(15, 53)", fit.left(15, 53), -9007199254740992); + +// Test fit.right +test("fit.right(12, 10)", fit.right(12, 10), 0); +test("fit.right(19, 2)", fit.right(19, 2), 4); +test("fit.right(-9007199254740992, 53)", fit.right(-9007199254740992, 53), 7); + +// Test fit.right_signed +test("fit.right_signed(-2, 1)", fit.right_signed(-2, 1), -1); + +// Test fit.mask +test("fit.mask(0)", fit.mask(0), 0); +test("fit.mask(1)", fit.mask(1), 1); +test("fit.mask(3)", fit.mask(3), 7); +test("fit.mask(8)", fit.mask(8), 255); +test("fit.mask(16)", fit.mask(16), 65535); +test("fit.mask(32)", fit.mask(32), 4294967295); +test("fit.mask(55)", fit.mask(55), 36028797018963967); +test("fit.mask(56)", fit.mask(56), -1); +test("fit.mask(57)", fit.mask(57), null); +test("fit.mask(-1)", fit.mask(-1), -2); +test("fit.mask(-3)", fit.mask(-3), -8); +test("fit.mask(-8)", fit.mask(-8), -256); +test("fit.mask(-16)", fit.mask(-16), -65536); +test("fit.mask(-32)", fit.mask(-32), -4294967296); +test("fit.mask(-55)", fit.mask(-55), -36028797018963968); +test("fit.mask(-56)", fit.mask(-56), 0); + +// Test fit.not +test("fit.not(0)", fit.not(0), -1); +test("fit.not(1)", fit.not(1), -2); +test("fit.not(-1)", fit.not(-1), 0); + +// Test fit.ones +test("fit.ones(-1)", fit.ones(-1), 56); +test("fit.ones(0)", fit.ones(0), 0); +test("fit.ones(8)", fit.ones(8), 1); +test("fit.ones(18)", fit.ones(18), 2); +test("fit.ones(255)", fit.ones(255), 8); + +// Test fit.zeros +test("fit.zeros(-1)", fit.zeros(-1), 0); +test("fit.zeros(0)", fit.zeros(0), 56); +test("fit.zeros(1)", fit.zeros(1), 55); +test("fit.zeros(2)", fit.zeros(2), 54); +test("fit.zeros(1024)", fit.zeros(1024), 45); + +// Test fit.rotate +test("fit.rotate(1, 1)", fit.rotate(1, 1), 2); +test("fit.rotate(-2, 1)", fit.rotate(-2, 1), -3); +test("fit.rotate(1, 56)", fit.rotate(1, 56), 1); // Full rotation +test("fit.rotate(1, -1)", fit.rotate(1, -1), 1 << 55); // Rotate right by 1 + +// Test fit.reverse +test("fit.reverse(-36028797018963968)", fit.reverse(-36028797018963968), 1); +test("fit.reverse(3141592653589793)", fit.reverse(3141592653589793), 2334719610726733); + +// Test edge cases and invalid inputs +test("fit.and with out-of-range", fit.and(1 << 56, 1), null); +test("fit.left with negative shift", fit.left(1, -1), null); +test("fit.left with large shift", fit.left(1, 100), null); +test("fit.right with negative shift", fit.right(1, -1), null); +test("fit.mask with float", fit.mask(3.5), null); + +// Print test summary +log.console("\n" + "=".repeat(50)); +log.console("Test Summary:"); +log.console(" Total tests run:", tests_run); +log.console(" Tests passed: ", tests_passed); +log.console(" Tests failed: ", tests_failed); +log.console(" Success rate: ", Math.round((tests_passed / tests_run) * 100) + "%"); +log.console("=".repeat(50)); + +if (tests_failed > 0) { + log.console("\nSome tests failed!"); +} else { + log.console("\nAll tests passed!"); +} + +$_.stop() \ No newline at end of file diff --git a/tests/jswota.ce b/tests/jswota.ce new file mode 100644 index 00000000..af177ce3 --- /dev/null +++ b/tests/jswota.ce @@ -0,0 +1,18 @@ +var text = use('text'); +var jswota = use('jswota'); + +log.console("Testing jswota headers:"); + +log.console("INT header:", text(jswota.INT, 'b')); +log.console("FP header:", text(jswota.FP, 'b')); +log.console("ARRAY header:", text(jswota.ARRAY, 'b')); +log.console("RECORD header:", text(jswota.RECORD, 'b')); +log.console("BLOB header:", text(jswota.BLOB, 'b')); +log.console("TEXT header:", text(jswota.TEXT, 'b')); +log.console("SYMBOL header:", text(jswota.SYMBOL, 'b')); + +log.console("4.25:" ,text(jswota.encode(4.25),'b')); +log.console("true:", text(jswota.encode(true),'b')) +log.console("record:", text(jswota.encode({a:5,b:7}),'b')) + +$_.stop() diff --git a/tests/wota.ce b/tests/wota.ce index e85cf5a7..a0944c2c 100644 --- a/tests/wota.ce +++ b/tests/wota.ce @@ -1,260 +1,221 @@ -var wota = use('wota'); -var os = use('os'); +/* + * wota_test.cm – self‑contained test‑suite for the Wota encode/decode module + * *** rewritten to run in an environment that ONLY supports + * Blobs; TypedArrays / ArrayBuffers / DataView are GONE. *** + * + * Exit status 0 → all tests passed, non‑zero otherwise. + */ -// Helper function to convert hex string to ArrayBuffer -function hexToBuffer(hex) { - let bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) - bytes[i / 2] = parseInt(hex.substr(i, 2), 16); - return bytes.buffer; +'use strict' + +var wota = use('wota') +var os = use('os') +var Blob = use('blob') + +/*──────────────────────────────────────────────────────────────────────────*/ +/* Helper utilities */ +/*──────────────────────────────────────────────────────────────────────────*/ + +const EPSILON = 1e-12 + +function stone_if_needed(b) { if (!stone.p(b)) stone(b) } + +/* Convert an array of octets to a stone Blob */ +function bytes_to_blob(bytes) { + var b = new Blob() + for (var i = 0; i < bytes.length; i++) { + var byte = bytes[i] + for (var bit = 7; bit >= 0; bit--) b.write_bit((byte >> bit) & 1) + } + stone(b) + return b } -// Helper function to convert ArrayBuffer to hex string -function bufferToHex(buffer) { - return Array.from(new Uint8Array(buffer)) - .map(b => b.toString(16).padStart(2, '0')) - .join('') - .toLowerCase(); +/* Parse hex → Blob */ +function hex_to_blob(hex) { + if (hex.length % 2) hex = '0' + hex // odd nibble safety + var bytes = [] + for (var i = 0; i < hex.length; i += 2) + bytes.push(parseInt(hex.substr(i, 2), 16)) + return bytes_to_blob(bytes) } -var EPSILON = 1e-12; +/* Blob → lower‑case hex */ +function blob_to_hex(blob) { + stone_if_needed(blob) + var bytes = [] + for (var i = 0; i < blob.length; i += 8) { + var byte = 0 + for (var bit = 0; bit < 8; bit++) byte = (byte << 1) | (blob.read_logical(i + bit) ? 1 : 0) + bytes.push(byte) + } + return bytes.map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() +} -// Deep comparison function for objects and arrays -function deepCompare(expected, actual, path = '') { - if (expected === actual) return { passed: true, messages: [] }; +function is_blob(x) { return x && typeof x === 'object' && typeof x.length === 'number' && typeof x.read_logical === 'function' } + +/* Deep comparison capable of Blobs + tolerance for floating diff */ +function deep_compare(expected, actual, path = '') { + if (expected === actual) return { passed: true, messages: [] } if (typeof expected === 'number' && typeof actual === 'number') { - if (isNaN(expected) && isNaN(actual)) - return { passed: true, messages: [] }; - const diff = Math.abs(expected - actual); - if (diff <= EPSILON) - return { passed: true, messages: [] }; - return { - passed: false, - messages: [ - `Value mismatch at ${path}: expected ${expected}, got ${actual}`, - `Difference of ${diff} is larger than tolerance ${EPSILON}` - ] - }; + if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] } + var diff = Math.abs(expected - actual) + if (diff <= EPSILON) return { passed: true, messages: [] } + return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] } } - if (expected instanceof ArrayBuffer && actual instanceof ArrayBuffer) { - const expArray = Array.from(new Uint8Array(expected)); - const actArray = Array.from(new Uint8Array(actual)); - return deepCompare(expArray, actArray, path); + if (is_blob(expected) && is_blob(actual)) { + stone_if_needed(expected); stone_if_needed(actual) + if (expected.length !== actual.length) + return { passed: false, messages: [`Blob length mismatch at ${path}: ${expected.length} vs ${actual.length}`] } + for (var i = 0; i < expected.length; i++) { + if (expected.read_logical(i) !== actual.read_logical(i)) + return { passed: false, messages: [`Blob bit mismatch at ${path}[${i}]`] } + } + return { passed: true, messages: [] } } - if (actual instanceof ArrayBuffer) - actual = Array.from(new Uint8Array(actual)); - if (Array.isArray(expected) && Array.isArray(actual)) { if (expected.length !== actual.length) - return { - passed: false, - messages: [`Array length mismatch at ${path}: expected ${expected.length}, got ${actual.length}`] - }; - let messages = []; - for (let i = 0; i < expected.length; i++) { - const result = deepCompare(expected[i], actual[i], `${path}[${i}]`); - if (!result.passed) messages.push(...result.messages); + return { passed: false, messages: [`Array length mismatch at ${path}: ${expected.length} vs ${actual.length}`] } + var msgs = [] + for (var i = 0; i < expected.length; i++) { + var res = deep_compare(expected[i], actual[i], `${path}[${i}]`) + if (!res.passed) msgs.push(...res.messages) } - return { passed: messages.length === 0, messages }; + return { passed: msgs.length === 0, messages: msgs } } - if (typeof expected === 'object' && expected !== null && - typeof actual === 'object' && actual !== null) { - const expKeys = Object.keys(expected).sort(); - const actKeys = Object.keys(actual).sort(); + if (typeof expected === 'object' && expected && typeof actual === 'object' && actual) { + var expKeys = Object.keys(expected).sort() + var actKeys = Object.keys(actual).sort() if (JSON.stringify(expKeys) !== JSON.stringify(actKeys)) - return { - passed: false, - messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`] - }; - let messages = []; - for (let key of expKeys) { - const result = deepCompare(expected[key], actual[key], `${path}.${key}`); - if (!result.passed) messages.push(...result.messages); + return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] } + var msgs = [] + for (var k of expKeys) { + var res = deep_compare(expected[k], actual[k], `${path}.${k}`) + if (!res.passed) msgs.push(...res.messages) } - return { passed: messages.length === 0, messages }; + return { passed: msgs.length === 0, messages: msgs } } - return { - passed: false, - messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`] - }; + return { passed: false, messages: [`Value mismatch at ${path}: ${JSON.stringify(expected)} vs ${JSON.stringify(actual)}`] } } -// Test cases covering Wota types and replacer/reviver functionality -var testarr = []; -var hex = "a374"; -for (var i = 0; i < 500; i++) { - testarr.push(1); - hex += "61"; -} +/*──────────────────────────────────────────────────────────────────────────*/ +/* Test matrix */ +/*──────────────────────────────────────────────────────────────────────────*/ + +var testarr = [] +var hex = 'a374' +for (var i = 0; i < 500; i++) { testarr.push(1); hex += '61' } + +function bb() { return bytes_to_blob.apply(null, arguments) } // shorthand var testCases = [ - // Integer tests (WOTA_INT up to 56-bit) - { input: 0, expectedHex: "60" }, - { input: 2023, expectedHex: "e08f67" }, - { input: -1, expectedHex: "69" }, - { input: 7, expectedHex: "67" }, - { input: -7, expectedHex: "6f" }, - { input: 1023, expectedHex: "e07f" }, - { input: -1023, expectedHex: "ef7f" }, - { input: 2**55 - 1, expectedHex: "e0ffffffffffffff" }, // Max 56-bit int - { input: -(2**55), expectedHex: "e000000000000000" }, // Min 56-bit int + { input: 0, expectedHex: '60' }, + { input: 2023, expectedHex: 'e08f67' }, + { input: -1, expectedHex: '69' }, + { input: 7, expectedHex: '67' }, + { input: -7, expectedHex: '6f' }, + { input: 1023, expectedHex: 'e07f' }, + { input: -1023, expectedHex: 'ef7f' }, + { input: Math.pow(2, 55) - 1, expectedHex: 'e0ffffffffffffff' }, + { input: -Math.pow(2, 55), expectedHex: 'e000000000000000' }, - // Symbol tests - { input: undefined, expectedHex: "70" }, - { input: false, expectedHex: "72" }, - { input: true, expectedHex: "73" }, + { input: undefined, expectedHex: '70' }, + { input: false, expectedHex: '72' }, + { input: true, expectedHex: '73' }, - // Floating Point tests (WOTA_FLOAT) - { input: -1.01, expectedHex: "5a65" }, - { input: 98.6, expectedHex: "51875a" }, - { input: -0.5772156649, expectedHex: "d80a95c0b0bd69" }, - { input: -1.00000000000001, expectedHex: "d80e96deb183e98001" }, - { input: -10000000000000, expectedHex: "c80d01" }, - { input: 2**55, expectedHex: "d80e01" }, // Beyond 56-bit, stored as float + { input: -1.01, expectedHex: '5a65' }, + { input: 98.6, expectedHex: '51875a' }, + { input: -0.5772156649, expectedHex: 'd80a95c0b0bd69' }, + { input: -1.00000000000001, expectedHex: 'd80e96deb183e98001' }, + { input: -10000000000000, expectedHex: 'c80d01' }, + { input: Math.pow(2, 55), expectedHex: 'd80e01' }, - // Text tests - { input: "", expectedHex: "10" }, - { input: "cat", expectedHex: "13636174" }, - { input: "U+1F4A9 πÇîπüåπéôπüíτ╡╡µûçσ¡ùπÇì ┬½≡ƒÆ⌐┬╗", - expectedHex: "9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b" }, + { input: '', expectedHex: '10' }, + { input: 'cat', expectedHex: '13636174' }, + { input: 'U+1F4A9 πÇîπüåπéôπüíτ╡╡µûçσ¡ùπÇì ┬½≡ƒÆ⌐┬╗', expectedHex: '9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b' }, - // Blob tests - { input: new Uint8Array([0xFF, 0xAA]).buffer, expectedHex: "8010ffaa" }, - { input: new Uint8Array([0b11110000, 0b11100011, 0b00100000, 0b10000000]).buffer, - expectedHex: "8019f0e32080" }, + { input: bytes_to_blob([0xff, 0xaa]), expectedHex: '8010ffaa' }, + { input: bytes_to_blob([0xf0, 0xe3, 0x20, 0x80]), expectedHex: '8019f0e32080' }, - // Large array test - { input: testarr, expectedHex: hex }, + { input: testarr, expectedHex: hex }, - // Array tests - { input: [], expectedHex: "20" }, - { input: [1, 2, 3], expectedHex: "23616263" }, - { input: [-1, 0, 1.5], expectedHex: "2369605043" }, + { input: [], expectedHex: '20' }, + { input: [1, 2, 3], expectedHex: '23616263' }, + { input: [-1, 0, 1.5], expectedHex: '2369605043' }, - // Record tests - { input: {}, expectedHex: "30" }, - { input: { a: 1, b: 2 }, expectedHex: "32116161116262" }, + { input: {}, expectedHex: '30' }, + { input: { a: 1, b: 2 }, expectedHex: '32116161116262' }, - // Complex nested structures - { input: { - num: 42, - arr: [1, -1, 2.5], - str: "test", - obj: { x: true } - }, - expectedHex: "34216e756d622a2173747214746573742161727223616965235840216f626a21117873" }, + { input: { num: 42, arr: [1, -1, 2.5], str: 'test', obj: { x: true } }, expectedHex: '34216e756d622a2173747214746573742161727223616965235840216f626a21117873' }, - // Additional edge cases - { input: new Uint8Array([]).buffer, expectedHex: "00" }, - { input: [[]], expectedHex: "2120" }, - { input: { "": "" }, expectedHex: "311010" }, - { input: 1e-10, expectedHex: "d00a01" }, + { input: new Blob(), expectedHex: '00' }, + { input: [[]], expectedHex: '2120' }, + { input: { '': '' }, expectedHex: '311010' }, + { input: 1e-10, expectedHex: 'd00a01' }, - // Replacer tests - { input: { a: 1, b: 2 }, - replacer: (key, value) => typeof value === 'number' ? value * 2 : value, - expected: { a: 2, b: 4 }, - testType: 'replacer' }, + { input: { a: 1, b: 2 }, replacer: (k, v) => typeof v === 'number' ? v * 2 : v, expected: { a: 2, b: 4 }, testType: 'replacer' }, + { input: { x: 'test', y: 5 }, replacer: (k, v) => k === 'x' ? v + '!' : v, expected: { x: 'test!', y: 5 }, testType: 'replacer' }, - { input: { x: "test", y: 5 }, - replacer: (key, value) => key === 'x' ? value + "!" : value, - expected: { x: "test!", y: 5 }, - testType: 'replacer' }, + { input: { a: 1, b: 2 }, reviver: (k, v) => typeof v === 'number' ? v * 3 : v, expected: { a: 3, b: 6 }, testType: 'reviver' }, + { input: { x: 'test', y: 10 }, reviver: (k, v) => k === 'y' ? v + 1 : v, expected: { x: 'test', y: 11 }, testType: 'reviver' } +] - // Reviver tests - { input: { a: 1, b: 2 }, - reviver: (key, value) => typeof value === 'number' ? value * 3 : value, - expected: { a: 3, b: 6 }, - testType: 'reviver' }, +/*──────────────────────────────────────────────────────────────────────────*/ +/* Execution */ +/*──────────────────────────────────────────────────────────────────────────*/ - { input: { x: "test", y: 10 }, - reviver: (key, value) => key === 'y' ? value + 1 : value, - expected: { x: "test", y: 11 }, - testType: 'reviver' } -]; +var results = [] +var testCount = 0 -// Run tests and collect results -let results = []; -let testCount = 0; - -for (let test of testCases) { - testCount++; - let testName = `Test ${testCount}: ${JSON.stringify(test.input)}${test.testType ? ` (${test.testType})` : ''}`; - let passed = true; - let messages = []; +for (var t of testCases) { + testCount++ + var name = `Test ${testCount}: ${JSON.stringify(t.input)}${t.testType ? ' (' + t.testType + ')' : ''}` + var passed = true + var msgs = [] try { - // Test encoding - let encoded = test.replacer ? wota.encode(test.input, test.replacer) : wota.encode(test.input); - if (!(encoded instanceof ArrayBuffer)) { - passed = false; - messages.push("Encode should return ArrayBuffer"); - } else { - if (test.expectedHex) { - let encodedHex = bufferToHex(encoded); - if (encodedHex !== test.expectedHex.toLowerCase()) { - messages.push( - `Hex encoding differs (informational): - Expected: ${test.expectedHex} - Got: ${encodedHex}` - ); - } + var enc = t.replacer ? wota.encode(t.input, t.replacer) : wota.encode(t.input) + if (!is_blob(enc)) { passed = false; msgs.push('encode() should return a Blob') } + else { + if (t.expectedHex) { + var gotHex = blob_to_hex(enc) + if (gotHex !== t.expectedHex.toLowerCase()) + msgs.push(`Hex encoding differs (info): exp ${t.expectedHex}, got ${gotHex}`) } - // Test decoding - let decoded = test.reviver ? wota.decode(encoded, test.reviver) : wota.decode(encoded); - let expected = test.expected || test.input; + var dec = t.reviver ? wota.decode(enc, t.reviver) : wota.decode(enc) + var exp = t.expected !== undefined ? t.expected : t.input - // Normalize ArrayBuffer for comparison - if (expected instanceof ArrayBuffer) - expected = Array.from(new Uint8Array(expected)); - if (decoded instanceof ArrayBuffer) - decoded = Array.from(new Uint8Array(decoded)); - - const compareResult = deepCompare(expected, decoded); - if (!compareResult.passed) { - passed = false; - messages.push("Decoding failed:"); - messages.push(...compareResult.messages); - } + var cmp = deep_compare(exp, dec) + if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) } } - } catch (e) { - passed = false; - messages.push(`Exception thrown: ${e}`); - } - - results.push({ testName, passed, messages }); + } catch (e) { passed = false; msgs.push('Exception: ' + e) } + results.push({ name, passed, msgs }) if (!passed) { - log.console(`\nDetailed Failure Report for ${testName}:`); - log.console(`Input: ${JSON.stringify(test.input)}`); - if (test.replacer) log.console(`Replacer: ${test.replacer.toString()}`); - if (test.reviver) log.console(`Reviver: ${test.reviver.toString()}`); - log.console(messages.join("\n")); - log.console(""); + log.console('\nFailure detail for ' + name + '\n' + msgs.join('\n') + '\n') } } -// Summary -log.console("\nTest Summary:"); -results.forEach(result => { - log.console(`${result.testName} - ${result.passed ? "Passed" : "Failed"}`); - if (!result.passed) - log.console(result.messages); -}); +/*──────────────────────────────────────────────────────────────────────────*/ +/* Summary */ +/*──────────────────────────────────────────────────────────────────────────*/ -let passedCount = results.filter(r => r.passed).length; -log.console(`\nResult: ${passedCount}/${testCount} tests passed`); - -if (passedCount < testCount) { - log.console("Overall: FAILED"); - os.exit(1); -} else { - log.console("Overall: PASSED"); - os.exit(0); +log.console('\nTest Summary:') +var passCount = 0 +for (var r of results) { + log.console(`${r.name} – ${r.passed ? 'Passed' : 'Failed'}`) + if (r.msgs.length && r.passed) log.console(' ' + r.msgs.join('\n ')) + if (r.passed) passCount++ } + +log.console(`\nResult: ${passCount}/${testCount} tests passed`) +if (passCount === testCount) { log.console('Overall: PASSED'); os.exit(0) } +log.console('Overall: FAILED') + +$_.stop() \ No newline at end of file