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',
'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 += [

View File

@@ -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,13 +276,10 @@ 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]
}
}
}
var blob = use('blob')
@@ -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
}

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_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

View File

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

View File

@@ -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, 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) {
free(data);
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;
}
// 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),

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_blob.h"
#define KIM_IMPLEMENTATION
#define NOTA_IMPLEMENTATION
#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;
}
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;
}

View File

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

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
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 → 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 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 },
// 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);
}
}
} catch (e) {
passed = false;
messages.push(`Exception thrown: ${e}`);
var cmp = deep_compare(exp, dec)
if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) }
}
} catch (e) { passed = false; msgs.push('Exception: ' + e) }
results.push({ testName, passed, messages });
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()