attempt for jswota in js

This commit is contained in:
2025-06-08 00:49:19 -05:00
parent 34dcd0a235
commit 3176e6775d
15 changed files with 839 additions and 241 deletions

View File

@@ -295,7 +295,7 @@ src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', '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', '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', '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 # quirc src
src += [ src += [

View File

@@ -66,7 +66,7 @@ function noop() {}
globalThis.log = new Proxy(logs, { globalThis.log = new Proxy(logs, {
get(target,prop,receiver) { get(target,prop,receiver) {
if (target[prop]) if (target[prop])
return (...args) => args.forEach(arg => target[prop](arg)) return (...args) => target[prop](args.join(' '))
return noop return noop
} }
@@ -254,7 +254,8 @@ var default_config = {
ar_timer: 60, ar_timer: 60,
actor_memory:0, actor_memory:0,
net_service:0.1, net_service:0.1,
reply_timeout:60 reply_timeout:60,
main: false,
} }
config.system ??= {} config.system ??= {}
@@ -275,11 +276,8 @@ function load_actor_config(program) {
} }
if (config.actors && config.actors[actor_name]) { if (config.actors && config.actors[actor_name]) {
// Merge actor config into cell.args for (var key in config.actors[actor_name])
for (var key in config.actors[actor_name]) {
log.console(`setting ${key}`)
cell.args[key] = config.actors[actor_name][key] 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) { function actor_send(actor, message) {
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
return return
@@ -573,7 +573,14 @@ function actor_send(actor, message) {
// message to actor in same flock // message to actor in same flock
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) { 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 return
} }

134
scripts/jswota.cm Normal file
View File

@@ -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 }

View File

@@ -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_blob(blob *b, const blob *src);
int blob_write_dec64(blob *b, double d); int blob_write_dec64(blob *b, double d);
int blob_write_int64(blob *b, int64_t i); 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_pad(blob *b, int block_size);
int blob_write_text(blob *b, const char *text); 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); 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_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_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_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); 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; 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) { int blob_write_pad(blob *b, int block_size) {
if (!b || b->is_stone) return -1; if (!b || b->is_stone) return -1;
if (block_size <= 0) 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; 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) { 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; if (!b || !b->is_stone || !out_text || !bits_read) return -1;
// Need at least 64 bits for length prefix // Need at least 64 bits for length prefix

View File

@@ -55,6 +55,7 @@
#include "qjs_sdl.h" #include "qjs_sdl.h"
#include "qjs_kim.h" #include "qjs_kim.h"
#include "qjs_utf8.h" #include "qjs_utf8.h"
#include "qjs_fit.h"
#ifndef NSTEAM #ifndef NSTEAM
#include "qjs_steam.h" #include "qjs_steam.h"
#endif #endif
@@ -1558,6 +1559,7 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(miniz)); arrput(rt->module_registry, MISTLINE(miniz));
arrput(rt->module_registry, MISTLINE(kim)); arrput(rt->module_registry, MISTLINE(kim));
arrput(rt->module_registry, MISTLINE(utf8)); arrput(rt->module_registry, MISTLINE(utf8));
arrput(rt->module_registry, MISTLINE(fit));
// power user // power user
arrput(rt->module_registry, MISTLINE(js)); arrput(rt->module_registry, MISTLINE(js));

View File

@@ -78,13 +78,13 @@ JSC_CCALL(os_createactor,
if (rt->disrupt) if (rt->disrupt)
return JS_ThrowInternalError(js, "Can't start a new actor while disrupting."); 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); create_actor(startup);
) )
JSC_CCALL(os_mailbox_push, JSC_CCALL(os_mailbox_push,
if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and a message"); 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]); const char *id = JS_ToCString(js, argv[0]);
int exist = actor_exists(id); int exist = actor_exists(id);
@@ -97,7 +97,7 @@ JSC_CCALL(os_mailbox_push,
cell_rt *target = get_actor(id); cell_rt *target = get_actor(id);
JS_FreeCString(js,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)) { if (!JS_IsUndefined(sys)) {
const char *k = JS_ToCString(js,sys); const char *k = JS_ToCString(js,sys);
int stop = 0; int stop = 0;
@@ -110,10 +110,15 @@ JSC_CCALL(os_mailbox_push,
if (stop) return JS_UNDEFINED; if (stop) return JS_UNDEFINED;
} }
*/
// void *data = value2wota(js, argv[1], JS_UNDEFINED, NULL);
size_t size;
void *data = js_get_blob_data(js, &size, argv[1]);
void *data = value2wota(js, argv[1], JS_UNDEFINED); void *copy = malloc(size);
memcpy(copy, data, size);
const char *err = send_message(id, data); const char *err = send_message(id, copy);
if (err) { if (err) {
free(data); free(data);
return JS_ThrowInternalError(js, "Could not send message: %s", err); return JS_ThrowInternalError(js, "Could not send message: %s", err);

View File

@@ -186,6 +186,29 @@ static JSValue js_blob_write_number(JSContext *ctx, JSValueConst this_val,
return JS_UNDEFINED; 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) // blob.write_kim(fit)
static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val, static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) { int argc, JSValueConst *argv) {
@@ -311,6 +334,40 @@ static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val,
return JS_NewFloat64(ctx, d); 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) // blob.read_text(from)
static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val, static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) { 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_bit", 1, js_blob_write_bit),
JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob), JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob),
JS_CFUNC_DEF("write_number", 1, js_blob_write_number), 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_text", 1, js_blob_write_text),
JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad), 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_logical", 1, js_blob_read_logical),
JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob), JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob),
JS_CFUNC_DEF("read_number", 1, js_blob_read_number), 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("read_text", 1, js_blob_read_text),
JS_CFUNC_DEF("pad?", 2, js_blob_pad_q), JS_CFUNC_DEF("pad?", 2, js_blob_pad_q),

257
source/qjs_fit.c Normal file
View File

@@ -0,0 +1,257 @@
#include "qjs_fit.h"
#include "jsffi.h"
#include <stdint.h>
#include <limits.h>
#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;
}

8
source/qjs_fit.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_FIT_H
#define QJS_FIT_H
#include <quickjs.h>
JSValue js_fit_use(JSContext *js);
#endif

View File

@@ -1,7 +1,6 @@
#include "qjs_nota.h" #include "qjs_nota.h"
#include "qjs_blob.h" #include "qjs_blob.h"
#define KIM_IMPLEMENTATION
#define NOTA_IMPLEMENTATION #define NOTA_IMPLEMENTATION
#include "nota.h" #include "nota.h"

View File

@@ -287,7 +287,7 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
return data_ptr; 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; WotaEncodeContext enc_s, *enc = &enc_s;
@@ -305,6 +305,7 @@ void *value2wota(JSContext *ctx, JSValue v, JSValue replacer)
JS_FreeValue(ctx, enc->visited_stack); JS_FreeValue(ctx, enc->visited_stack);
size_t total_bytes = enc->wb.size * sizeof(uint64_t); size_t total_bytes = enc->wb.size * sizeof(uint64_t);
void *wota = realloc(enc->wb.data, total_bytes); void *wota = realloc(enc->wb.data, total_bytes);
if (bytes) *bytes = total_bytes;
return wota; 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) 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"); if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument");
WotaEncodeContext enc_s, *enc = &enc_s; size_t total_bytes;
enc->ctx = ctx; void *wota = value2wota(ctx, argv[0], JS_IsFunction(ctx,argv[1]) ? argv[1] : JS_UNDEFINED, &total_bytes);
enc->visited_stack = JS_NewArray(ctx); JSValue ret = js_new_blob_stoned_copy(ctx, wota, total_bytes);
enc->cycle = 0; free(wota);
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);
return ret; return ret;
} }

View File

@@ -6,7 +6,7 @@
JSValue js_wota_use(JSContext*); JSValue js_wota_use(JSContext*);
void *value2wota(JSContext*, JSValue, JSValue); void *value2wota(JSContext*, JSValue val, JSValue replacer, size_t *bytes);
JSValue wota2value(JSContext*, void*); JSValue wota2value(JSContext*, void*);
#endif #endif

122
tests/fit.ce Normal file
View File

@@ -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()

18
tests/jswota.ce Normal file
View File

@@ -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()

View File

@@ -1,260 +1,221 @@
var wota = use('wota'); /*
var os = use('os'); * wota_test.cm  selfcontained testsuite 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, nonzero otherwise.
*/
// Helper function to convert hex string to ArrayBuffer 'use strict'
function hexToBuffer(hex) {
let bytes = new Uint8Array(hex.length / 2); var wota = use('wota')
for (let i = 0; i < hex.length; i += 2) var os = use('os')
bytes[i / 2] = parseInt(hex.substr(i, 2), 16); var Blob = use('blob')
return bytes.buffer;
/*──────────────────────────────────────────────────────────────────────────*/
/* 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 /* Parse hex → Blob */
function bufferToHex(buffer) { function hex_to_blob(hex) {
return Array.from(new Uint8Array(buffer)) if (hex.length % 2) hex = '0' + hex // odd nibble safety
.map(b => b.toString(16).padStart(2, '0')) var bytes = []
.join('') for (var i = 0; i < hex.length; i += 2)
.toLowerCase(); bytes.push(parseInt(hex.substr(i, 2), 16))
return bytes_to_blob(bytes)
} }
var EPSILON = 1e-12; /* Blob → lowercase 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 is_blob(x) { return x && typeof x === 'object' && typeof x.length === 'number' && typeof x.read_logical === 'function' }
function deepCompare(expected, actual, path = '') {
if (expected === actual) return { passed: true, messages: [] }; /* 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 (typeof expected === 'number' && typeof actual === 'number') {
if (isNaN(expected) && isNaN(actual)) if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }
return { passed: true, messages: [] }; var diff = Math.abs(expected - actual)
const diff = Math.abs(expected - actual); if (diff <= EPSILON) return { passed: true, messages: [] }
if (diff <= EPSILON) return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] }
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 (expected instanceof ArrayBuffer && actual instanceof ArrayBuffer) { if (is_blob(expected) && is_blob(actual)) {
const expArray = Array.from(new Uint8Array(expected)); stone_if_needed(expected); stone_if_needed(actual)
const actArray = Array.from(new Uint8Array(actual)); if (expected.length !== actual.length)
return deepCompare(expArray, actArray, path); 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 (Array.isArray(expected) && Array.isArray(actual)) {
if (expected.length !== actual.length) if (expected.length !== actual.length)
return { return { passed: false, messages: [`Array length mismatch at ${path}: ${expected.length} vs ${actual.length}`] }
passed: false, var msgs = []
messages: [`Array length mismatch at ${path}: expected ${expected.length}, got ${actual.length}`] for (var i = 0; i < expected.length; i++) {
}; var res = deep_compare(expected[i], actual[i], `${path}[${i}]`)
let messages = []; if (!res.passed) msgs.push(...res.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: messages.length === 0, messages }; return { passed: msgs.length === 0, messages: msgs }
} }
if (typeof expected === 'object' && expected !== null && if (typeof expected === 'object' && expected && typeof actual === 'object' && actual) {
typeof actual === 'object' && actual !== null) { var expKeys = Object.keys(expected).sort()
const expKeys = Object.keys(expected).sort(); var actKeys = Object.keys(actual).sort()
const actKeys = Object.keys(actual).sort();
if (JSON.stringify(expKeys) !== JSON.stringify(actKeys)) if (JSON.stringify(expKeys) !== JSON.stringify(actKeys))
return { return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] }
passed: false, var msgs = []
messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`] for (var k of expKeys) {
}; var res = deep_compare(expected[k], actual[k], `${path}.${k}`)
let messages = []; if (!res.passed) msgs.push(...res.messages)
for (let key of expKeys) {
const result = deepCompare(expected[key], actual[key], `${path}.${key}`);
if (!result.passed) messages.push(...result.messages);
} }
return { passed: messages.length === 0, messages }; return { passed: msgs.length === 0, messages: msgs }
} }
return { return { passed: false, messages: [`Value mismatch at ${path}: ${JSON.stringify(expected)} vs ${JSON.stringify(actual)}`] }
passed: false,
messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`]
};
} }
// Test cases covering Wota types and replacer/reviver functionality /*──────────────────────────────────────────────────────────────────────────*/
var testarr = []; /* Test matrix */
var hex = "a374"; /*──────────────────────────────────────────────────────────────────────────*/
for (var i = 0; i < 500; i++) {
testarr.push(1); var testarr = []
hex += "61"; 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 = [ var testCases = [
// Integer tests (WOTA_INT up to 56-bit) { input: 0, expectedHex: '60' },
{ input: 0, expectedHex: "60" }, { input: 2023, expectedHex: 'e08f67' },
{ input: 2023, expectedHex: "e08f67" }, { input: -1, expectedHex: '69' },
{ input: -1, expectedHex: "69" }, { input: 7, expectedHex: '67' },
{ input: 7, expectedHex: "67" }, { input: -7, expectedHex: '6f' },
{ input: -7, expectedHex: "6f" }, { input: 1023, expectedHex: 'e07f' },
{ input: 1023, expectedHex: "e07f" }, { input: -1023, expectedHex: 'ef7f' },
{ input: -1023, expectedHex: "ef7f" }, { input: Math.pow(2, 55) - 1, expectedHex: 'e0ffffffffffffff' },
{ input: 2**55 - 1, expectedHex: "e0ffffffffffffff" }, // Max 56-bit int { input: -Math.pow(2, 55), expectedHex: 'e000000000000000' },
{ input: -(2**55), expectedHex: "e000000000000000" }, // Min 56-bit int
// Symbol tests { input: undefined, expectedHex: '70' },
{ input: undefined, expectedHex: "70" }, { input: false, expectedHex: '72' },
{ input: false, expectedHex: "72" }, { input: true, expectedHex: '73' },
{ input: true, expectedHex: "73" },
// Floating Point tests (WOTA_FLOAT) { input: -1.01, expectedHex: '5a65' },
{ input: -1.01, expectedHex: "5a65" }, { input: 98.6, expectedHex: '51875a' },
{ input: 98.6, expectedHex: "51875a" }, { input: -0.5772156649, expectedHex: 'd80a95c0b0bd69' },
{ input: -0.5772156649, expectedHex: "d80a95c0b0bd69" }, { input: -1.00000000000001, expectedHex: 'd80e96deb183e98001' },
{ input: -1.00000000000001, expectedHex: "d80e96deb183e98001" }, { input: -10000000000000, expectedHex: 'c80d01' },
{ input: -10000000000000, expectedHex: "c80d01" }, { input: Math.pow(2, 55), expectedHex: 'd80e01' },
{ input: 2**55, expectedHex: "d80e01" }, // Beyond 56-bit, stored as float
// Text tests { input: '', expectedHex: '10' },
{ input: "", expectedHex: "10" }, { input: 'cat', expectedHex: '13636174' },
{ input: "cat", expectedHex: "13636174" }, { input: 'U+1F4A9 「うんち絵文字」 «💩»', expectedHex: '9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b' },
{ input: "U+1F4A9 「うんち絵文字」 «💩»",
expectedHex: "9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b" },
// Blob tests { input: bytes_to_blob([0xff, 0xaa]), expectedHex: '8010ffaa' },
{ input: new Uint8Array([0xFF, 0xAA]).buffer, expectedHex: "8010ffaa" }, { input: bytes_to_blob([0xf0, 0xe3, 0x20, 0x80]), expectedHex: '8019f0e32080' },
{ input: new Uint8Array([0b11110000, 0b11100011, 0b00100000, 0b10000000]).buffer,
expectedHex: "8019f0e32080" },
// Large array test { input: testarr, expectedHex: hex },
{ input: testarr, expectedHex: hex },
// Array tests { input: [], expectedHex: '20' },
{ input: [], expectedHex: "20" }, { input: [1, 2, 3], expectedHex: '23616263' },
{ input: [1, 2, 3], expectedHex: "23616263" }, { input: [-1, 0, 1.5], expectedHex: '2369605043' },
{ input: [-1, 0, 1.5], expectedHex: "2369605043" },
// Record tests { input: {}, expectedHex: '30' },
{ input: {}, expectedHex: "30" }, { input: { a: 1, b: 2 }, expectedHex: '32116161116262' },
{ 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 Blob(), expectedHex: '00' },
{ input: new Uint8Array([]).buffer, expectedHex: "00" }, { input: [[]], expectedHex: '2120' },
{ input: [[]], expectedHex: "2120" }, { input: { '': '' }, expectedHex: '311010' },
{ input: { "": "" }, expectedHex: "311010" }, { input: 1e-10, expectedHex: 'd00a01' },
{ input: 1e-10, expectedHex: "d00a01" },
// Replacer tests { input: { a: 1, b: 2 }, replacer: (k, v) => typeof v === 'number' ? v * 2 : v, expected: { a: 2, b: 4 }, testType: 'replacer' },
{ input: { a: 1, b: 2 }, { input: { x: 'test', y: 5 }, replacer: (k, v) => k === 'x' ? v + '!' : v, expected: { x: 'test!', y: 5 }, testType: 'replacer' },
replacer: (key, value) => typeof value === 'number' ? value * 2 : value,
expected: { a: 2, b: 4 },
testType: 'replacer' },
{ input: { x: "test", y: 5 }, { input: { a: 1, b: 2 }, reviver: (k, v) => typeof v === 'number' ? v * 3 : v, expected: { a: 3, b: 6 }, testType: 'reviver' },
replacer: (key, value) => key === 'x' ? value + "!" : value, { input: { x: 'test', y: 10 }, reviver: (k, v) => k === 'y' ? v + 1 : v, expected: { x: 'test', y: 11 }, testType: 'reviver' }
expected: { x: "test!", y: 5 }, ]
testType: 'replacer' },
// Reviver tests /*──────────────────────────────────────────────────────────────────────────*/
{ input: { a: 1, b: 2 }, /* Execution */
reviver: (key, value) => typeof value === 'number' ? value * 3 : value, /*──────────────────────────────────────────────────────────────────────────*/
expected: { a: 3, b: 6 },
testType: 'reviver' },
{ input: { x: "test", y: 10 }, var results = []
reviver: (key, value) => key === 'y' ? value + 1 : value, var testCount = 0
expected: { x: "test", y: 11 },
testType: 'reviver' }
];
// Run tests and collect results for (var t of testCases) {
let results = []; testCount++
let testCount = 0; var name = `Test ${testCount}: ${JSON.stringify(t.input)}${t.testType ? ' (' + t.testType + ')' : ''}`
var passed = true
for (let test of testCases) { var msgs = []
testCount++;
let testName = `Test ${testCount}: ${JSON.stringify(test.input)}${test.testType ? ` (${test.testType})` : ''}`;
let passed = true;
let messages = [];
try { try {
// Test encoding var enc = t.replacer ? wota.encode(t.input, t.replacer) : wota.encode(t.input)
let encoded = test.replacer ? wota.encode(test.input, test.replacer) : wota.encode(test.input); if (!is_blob(enc)) { passed = false; msgs.push('encode() should return a Blob') }
if (!(encoded instanceof ArrayBuffer)) { else {
passed = false; if (t.expectedHex) {
messages.push("Encode should return ArrayBuffer"); var gotHex = blob_to_hex(enc)
} else { if (gotHex !== t.expectedHex.toLowerCase())
if (test.expectedHex) { msgs.push(`Hex encoding differs (info): exp ${t.expectedHex}, got ${gotHex}`)
let encodedHex = bufferToHex(encoded);
if (encodedHex !== test.expectedHex.toLowerCase()) {
messages.push(
`Hex encoding differs (informational):
Expected: ${test.expectedHex}
Got: ${encodedHex}`
);
}
} }
// Test decoding var dec = t.reviver ? wota.decode(enc, t.reviver) : wota.decode(enc)
let decoded = test.reviver ? wota.decode(encoded, test.reviver) : wota.decode(encoded); var exp = t.expected !== undefined ? t.expected : t.input
let expected = test.expected || test.input;
// Normalize ArrayBuffer for comparison var cmp = deep_compare(exp, dec)
if (expected instanceof ArrayBuffer) if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) }
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);
}
} }
} catch (e) { } catch (e) { passed = false; msgs.push('Exception: ' + e) }
passed = false;
messages.push(`Exception thrown: ${e}`);
}
results.push({ testName, passed, messages });
results.push({ name, passed, msgs })
if (!passed) { if (!passed) {
log.console(`\nDetailed Failure Report for ${testName}:`); log.console('\nFailure detail for ' + name + '\n' + msgs.join('\n') + '\n')
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("");
} }
} }
// Summary /*──────────────────────────────────────────────────────────────────────────*/
log.console("\nTest Summary:"); /* Summary */
results.forEach(result => { /*──────────────────────────────────────────────────────────────────────────*/
log.console(`${result.testName} - ${result.passed ? "Passed" : "Failed"}`);
if (!result.passed)
log.console(result.messages);
});
let passedCount = results.filter(r => r.passed).length; log.console('\nTest Summary:')
log.console(`\nResult: ${passedCount}/${testCount} tests passed`); var passCount = 0
for (var r of results) {
if (passedCount < testCount) { log.console(`${r.name} ${r.passed ? 'Passed' : 'Failed'}`)
log.console("Overall: FAILED"); if (r.msgs.length && r.passed) log.console(' ' + r.msgs.join('\n '))
os.exit(1); if (r.passed) passCount++
} else {
log.console("Overall: PASSED");
os.exit(0);
} }
log.console(`\nResult: ${passCount}/${testCount} tests passed`)
if (passCount === testCount) { log.console('Overall: PASSED'); os.exit(0) }
log.console('Overall: FAILED')
$_.stop()