diff --git a/bench.ce b/bench.ce index 8dc505b9..338cfdbd 100644 --- a/bench.ce +++ b/bench.ce @@ -3,7 +3,7 @@ var pkg = use('package') var fd = use('fd') var time = use('time') var json = use('json') -var utf8 = use('utf8') +var blob = use('blob') var os = use('os') var testlib = use('internal/testlib') var math = use('math/radians') @@ -524,7 +524,7 @@ log.console(`Benchmarks: ${total_benches} total`) // Generate reports function generate_reports() { - var timestamp = number.floor(time.number()).toString() + var timestamp = text(number.floor(time.number())) var report_dir = shop.get_reports_dir() + '/bench_' + timestamp testlib.ensure_dir(report_dir) @@ -578,7 +578,7 @@ Total benchmarks: ${total_benches} } testlib.ensure_dir(report_dir) - fd.slurpwrite(`${report_dir}/bench.txt`, utf8.encode(txt_report)) + fd.slurpwrite(`${report_dir}/bench.txt`, stone(new blob(txt_report))) log.console(`Report written to ${report_dir}/bench.txt`) // Generate JSON per package @@ -595,7 +595,7 @@ Total benchmarks: ${total_benches} } var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json` - fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_benches))) + fd.slurpwrite(json_path, stone(new blob(json.encode(pkg_benches)))) } } diff --git a/build.cm b/build.cm index 1d36eb39..c4c2d869 100644 --- a/build.cm +++ b/build.cm @@ -8,7 +8,7 @@ var fd = use('fd') var crypto = use('crypto') -var utf8 = use('utf8') +var blob = use('blob') var os = use('os') var toolchains = use('toolchains') var shop = use('internal/shop') @@ -73,7 +73,7 @@ Build.detect_host_target = function() { // ============================================================================ function content_hash(str) { - return text(crypto.blake2(utf8.encode(str)), 'h') + return text(crypto.blake2(new blob(str)), 'h') } function get_build_dir() { diff --git a/internal/engine.cm b/internal/engine.cm index 9107b249..11e0da36 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -28,18 +28,6 @@ function use_embed(name) { return load_internal(`js_${name}_use`) } -globalThis.meme = function(obj, ...mixins) { - var result = _ObjectCreate(obj) - - array.for(mixins, mix => { - if (is_object(mix)) { - for (var key in mix) - result[key] = mix[key] - } - }) - return result -} - globalThis.logical = function(val1) { if (val1 == 0 || val1 == false || val1 == "false" || val1 == null) @@ -49,7 +37,6 @@ globalThis.logical = function(val1) return null; } -var utf8 = use_embed('utf8') var js = use_embed('js') var fd = use_embed('fd') @@ -69,13 +56,6 @@ if (!fd.is_dir(core_path)) { var use_cache = {} use_cache['core/os'] = os -var _Symbol = Symbol - -globalThis.key = function() -{ - return _Symbol() -} - // Load a core module from the file system function use_core(path) { var cache_key = 'core/' + path @@ -89,7 +69,7 @@ function use_core(path) { if (fd.is_file(file_path)) { var script_blob = fd.slurp(file_path) - var script = utf8.decode(script_blob) + var script = text(script_blob) var mod = `(function setup_module(use){${script}})` var fn = js.eval('core:' + path, mod) var result = fn.call(sym, use_core); @@ -104,13 +84,6 @@ function use_core(path) { var blob = use_core('blob') // Capture Object and Array methods before they're deleted -var _Object = Object -var _ObjectKeys = Object.keys -var _ObjectIsFrozen = Object.isFrozen -var _ObjectDefineProperty = Object.defineProperty -var _ObjectGetPrototypeOf = Object.getPrototypeOf -var _ObjectCreate = Object.create - Object.prototype.toString = function() { return json.encode(this) @@ -125,41 +98,9 @@ var actor_mod = use_core('actor') var wota = use_core('wota') var nota = use_core('nota') -// Global utility functions (use already-captured references) -var _keys = _ObjectKeys -var _getPrototypeOf = _ObjectGetPrototypeOf -var _create = _ObjectCreate - -globalThis.reverse = function(value) { - if (_isArray(value)) { - var result = [] - for (var i = value.length - 1; i >= 0; i--) { - result.push(value[i]) - } - return result - } - - // For blobs, would need blob module support - if (isa(value, blob)) { - // Simplified: return null for now, would need proper blob reversal - return null - } - - return null -} - globalThis.isa = function(value, master) { if (master == null) return false - // isa(value, array) - check if object has all keys - if (_isArray(master)) { - if (typeof value != 'object' || value == null) return false - for (var i = 0; i < master.length; i++) { - if (!(master[i] in value)) return false - } - return true - } - // isa(value, function) - check if function.prototype is in chain if (typeof master == 'function') { // Special type checks @@ -196,55 +137,9 @@ globalThis.isa = function(value, master) { return false } -globalThis.proto = function(obj) { - if (!isa(obj, object)) return null - var p = _getPrototypeOf(obj) - if (p == _Object.prototype) return null - return p -} - -globalThis.splat = function(obj) { - if (typeof obj != 'object' || obj == null) return null - - var result = {} - var current = obj - - // Walk prototype chain and collect text keys - while (current != null) { - var keys = _keys(current) - for (var i = 0; i < keys.length; i++) { - var k = keys[i] - if (!(k in result)) { - var val = current[k] - // Only include serializable types - if (typeof val == 'object' || typeof val == 'number' || - typeof val == 'string' || typeof val == 'boolean') { - result[k] = val - } - } - } - current = _getPrototypeOf(current) - } - - // Call to_data if present - if (typeof obj.to_data == 'function') { - var extra = obj.to_data(result) - if (typeof extra == 'object' && extra != null) { - var extraKeys = _keys(extra) - for (var i = 0; i < extraKeys.length; i++) { - result[extraKeys[i]] = extra[extraKeys[i]] - } - } - } - - return result -} - var ENETSERVICE = 0.1 var REPLYTIMEOUT = 60 // seconds before replies are ignored -globalThis.pi = 3.14159265358979323846264338327950288419716939937510 - function caller_data(depth = 0) { var file = "nofile" @@ -453,7 +348,7 @@ function guid(bits = 256) return text(guid,'h') } -var HEADER = _Symbol() +var HEADER = key() // takes a function input value that will eventually be called with the current time in number form. $_.clock = function(fn) { @@ -771,7 +666,6 @@ function turn(msg) } //log.console(`FIXME: need to get main from config, not just set to true`) -//log.console(`FIXME: remove global access (ie globalThis.use)`) //log.console(`FIXME: add freeze/unfreeze at this level, so we can do it (but scripts cannot)`) actor_mod.register_actor(_cell.id, turn, true, config.ar_timer) @@ -911,69 +805,11 @@ if (!locator) { if (!locator) throw new Error(`Main program ${_cell.args.program} could not be found`) -// Hide JavaScript built-ins - make them inaccessible -// Store references we need internally before deleting -var _Array = Array -var _String = String -var _Math = Math -var _Function = Function - -var _JSON = JSON - -// juicing these before Math is gone - -use_core('math/radians') -use_core('math/cycles') -use_core('math/degrees') - -// Delete from globalThis -delete globalThis.Object -delete globalThis.Math -delete globalThis.Number -delete globalThis.String -delete globalThis.Array -delete globalThis.Boolean -delete globalThis.Date -delete globalThis.Function -delete globalThis.Reflect -delete globalThis.Proxy -delete globalThis.WeakMap -delete globalThis.WeakSet -delete globalThis.WeakRef -delete globalThis.BigInt -delete globalThis.Symbol -//delete globalThis.Map -//delete globalThis.Set -delete globalThis.Promise -delete globalThis.ArrayBuffer -delete globalThis.DataView -delete globalThis.Int8Array -delete globalThis.Uint8Array -delete globalThis.Uint8ClampedArray -delete globalThis.Int16Array -delete globalThis.Uint16Array -delete globalThis.Int32Array -delete globalThis.Uint32Array -delete globalThis.Float32Array -delete globalThis.Float64Array -delete globalThis.BigInt64Array -delete globalThis.BigUint64Array -delete globalThis.eval -delete globalThis.parseInt -delete globalThis.parseFloat -delete globalThis.isNaN -delete globalThis.isFinite -delete globalThis.decodeURI -delete globalThis.decodeURIComponent -delete globalThis.encodeURI -delete globalThis.encodeURIComponent -delete globalThis.escape -delete globalThis.unescape -delete globalThis.Intl -delete globalThis.RegExp - stone(globalThis) +var rads = use_core("math/radians") +log.console(rads) + $_.clock(_ => { // Get capabilities for the main program var file_info = shop.file_info ? shop.file_info(locator.path) : null diff --git a/internal/shop.cm b/internal/shop.cm index c1e87d44..fa171b74 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -6,7 +6,6 @@ var miniz = use('miniz') var time = use('time') var js = use('js') var crypto = use('crypto') -var utf8 = use('utf8') var blob = use('blob') var pkg_tools = use('package') var os = use('os') @@ -277,7 +276,7 @@ Shop.load_lock = function() { // Save lock.toml configuration (to global shop) Shop.save_lock = function(lock) { var path = global_shop_path + '/lock.toml' - fd.slurpwrite(path, utf8.encode(toml.encode(lock))); + fd.slurpwrite(path, stone(new blob(toml.encode(lock)))); } @@ -415,7 +414,7 @@ function resolve_mod_fn(path, pkg) { var content = text(fd.slurp(path)) var script = script_form(path, content, file_pkg, inject); - var obj = pull_from_cache(utf8.encode(script)) + var obj = pull_from_cache(stone(new blob(script))) if (obj) { var fn = js.compile_unblob(obj) return js.eval_compile(fn) @@ -427,7 +426,7 @@ function resolve_mod_fn(path, pkg) { var fn = js.compile(compile_name, script) - put_into_cache(utf8.encode(script), js.compile_blob(fn)) + put_into_cache(stone(new blob(script)), js.compile_blob(fn)) return js.eval_compile(fn) } @@ -608,7 +607,8 @@ Shop.open_package_dylib = function(pkg) { } // If no package context, only check core internal symbols - if (!package_context) { + if (!package_context || package_context == 'core') { + path = path.replace('/', '_') var core_sym = `js_${path}_use` if (os.internal_exists(core_sym)) { return { diff --git a/link.cm b/link.cm index 78747a08..867066f1 100644 --- a/link.cm +++ b/link.cm @@ -3,7 +3,7 @@ var toml = use('toml') var fd = use('fd') -var utf8 = use('utf8') +var blob = use('blob') var os = use('os') var global_shop_path = os.global_shop_path @@ -81,7 +81,7 @@ Link.save = function(links) { link_cache = links var cfg = { links: links } var path = get_links_path() - fd.slurpwrite(path, utf8.encode(toml.encode(cfg))) + fd.slurpwrite(path, new blob(toml.encode(cfg))) } Link.add = function(canonical, target, shop) { diff --git a/math/cycles.cm b/math/cycles.cm deleted file mode 100644 index d0167a1c..00000000 --- a/math/cycles.cm +++ /dev/null @@ -1,18 +0,0 @@ -var cycles = {} -var Math_obj = Math - -cycles.arc_cosine = function(x) { return Math_obj.acos(x) / (2 * pi) } -cycles.arc_sine = function(x) { return Math_obj.asin(x) / (2 * pi) } -cycles.arc_tangent = function(x) { return Math_obj.atan(x) / (2 * pi) } -cycles.cosine = function(x) { return Math_obj.cos(x * 2 * pi) } -cycles.e = Math_obj.E -cycles.ln = function(x) { return Math_obj.log(x) } -cycles.log = function(x) { return Math_obj.log10(x) } -cycles.log2 = function(x) { return Math_obj.log2(x) } -cycles.power = function(x, y) { return Math_obj.pow(x, y) } -cycles.root = function(x, y) { return Math_obj.pow(x, 1 / y) } -cycles.sine = function(x) { return Math_obj.sin(x * 2 * pi) } -cycles.sqrt = function(x) { return Math_obj.sqrt(x) } -cycles.tangent = function(x) { return Math_obj.tan(x * 2 * pi) } - -return cycles diff --git a/math/degrees.cm b/math/degrees.cm deleted file mode 100644 index 176eea06..00000000 --- a/math/degrees.cm +++ /dev/null @@ -1,21 +0,0 @@ -var degrees = {} -var Math_obj = Math - -var deg2rad = pi / 180 -var rad2deg = 180 / pi - -return { - arc_cosine: function(x) { return Math_obj.acos(x) * rad2deg }, - arc_sine: function(x) { return Math_obj.asin(x) * rad2deg }, - arc_tangent: function(x) { return Math_obj.atan(x) * rad2deg }, - cosine: function(x) { return Math_obj.cos(x * deg2rad) }, - e: Math_obj.E, - ln: function(x) { return Math_obj.log(x) }, - log: function(x) { return Math_obj.log10(x) }, - log2: function(x) { return Math_obj.log2(x) }, - power: function(x, y) { return Math_obj.pow(x, y) }, - root: function(x, y) { return Math_obj.pow(x, 1/y) }, - sine: function(x) { return Math_obj.sin(x * deg2rad) }, - sqrt: function(x) { return Math_obj.sqrt(x) }, - tangent: function(x) { return Math_obj.tan(x * deg2rad) } -} diff --git a/math/radians.cm b/math/radians.cm deleted file mode 100644 index 9fbc29c6..00000000 --- a/math/radians.cm +++ /dev/null @@ -1,17 +0,0 @@ -var Math = globalThis.Math - -return { - arc_cosine: Math.acos, - arc_sine: Math.asin, - arc_tangent: Math.atan, - cosine: Math.cos, - e: Math.E, - ln: Math.log, - log: Math.log10, - log2: Math.log2, - power: Math.pow, - root: function(x, n) { return Math.pow(x, 1/n) }, - sine: Math.sin, - sqrt: Math.sqrt, - tangent: Math.tan -} diff --git a/meson.build b/meson.build index 62684339..c102f144 100644 --- a/meson.build +++ b/meson.build @@ -57,7 +57,6 @@ scripts = [ 'wildstar.c', 'fit.c', 'crypto.c', - 'utf8.c', 'internal/kim.c', 'time.c', 'internal/nota.c', @@ -68,7 +67,6 @@ scripts = [ 'net/enet.c', 'wildstar.c', 'archive/miniz.c', - 'internal/json.c', ] foreach file: scripts diff --git a/source/quickjs.c b/source/quickjs.c index 1b915a0e..f441d337 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -1928,7 +1928,6 @@ JSContext *JS_NewContext(JSRuntime *rt) JS_AddIntrinsicEval(ctx); JS_AddIntrinsicStringNormalize(ctx); JS_AddIntrinsicRegExp(ctx); - JS_AddIntrinsicJSON(ctx); JS_AddIntrinsicMapSet(ctx); return ctx; @@ -26385,7 +26384,6 @@ static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, ret = JS_EvalInternal(ctx, this_obj, str, len, "", flags, scope_idx); JS_FreeCString(ctx, str); return ret; - } JSValue JS_EvalThis(JSContext *ctx, JSValueConst this_obj, @@ -39499,6 +39497,19 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, } bd = blob_new_from_blob(src, (size_t)from, (size_t)to); } + /* blob(text) - create blob from UTF-8 string */ + else if (argc == 1 && (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_STRING || JS_VALUE_GET_TAG(argv[0]) == JS_TAG_STRING_ROPE)) { + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) + return JS_EXCEPTION; + size_t len = strlen(str); + bd = blob_new(len * 8); + if (bd) { + memcpy(bd->data, str, len); + bd->length = len * 8; + } + JS_FreeCString(ctx, str); + } else { return JS_ThrowTypeError(ctx, "blob constructor: invalid arguments"); } @@ -40057,6 +40068,230 @@ static const JSCFunctionListEntry js_cell_stone_funcs[] = { JS_CFUNC_DEF("p", 1, js_cell_stone_p), }; +/* ============================================================================ + * key() function - create a unique symbol + * ============================================================================ */ + +static JSValue js_cell_key(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewSymbol(ctx, JS_ATOM_NULL, JS_ATOM_TYPE_SYMBOL); +} + +/* ============================================================================ + * reverse() function - reverse an array + * ============================================================================ */ + +static JSValue js_cell_reverse(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_NULL; + + JSValue value = argv[0]; + + /* Handle arrays */ + if (JS_IsArray(ctx, value)) { + JSValue length_val = JS_GetPropertyStr(ctx, value, "length"); + int64_t len; + if (JS_ToInt64(ctx, &len, length_val) < 0) { + JS_FreeValue(ctx, length_val); + return JS_NULL; + } + JS_FreeValue(ctx, length_val); + + JSValue result = JS_NewArray(ctx); + for (int64_t i = len - 1, j = 0; i >= 0; i--, j++) { + JSValue elem = JS_GetPropertyInt64(ctx, value, i); + JS_SetPropertyInt64(ctx, result, j, elem); + } + return result; + } + + /* Handle blobs */ + blob *bd = js_get_blob(ctx, value); + if (bd) { + /* Blobs need proper blob reversal support - return null for now */ + return JS_NULL; + } + + return JS_NULL; +} + +/* ============================================================================ + * proto() function - get prototype of an object + * ============================================================================ */ + +static JSValue js_cell_proto(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_NULL; + + JSValue obj = argv[0]; + if (!JS_IsObject(obj)) + return JS_NULL; + + JSValue proto = JS_GetPrototype(ctx, obj); + if (JS_IsException(proto)) + return JS_NULL; + + /* If prototype is Object.prototype, return null */ + if (JS_IsObject(proto)) { + JSValue obj_proto = JS_GetPropertyStr(ctx, ctx->class_proto[JS_CLASS_OBJECT], ""); + if (JS_VALUE_GET_OBJ(proto) == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, obj_proto); + return JS_NULL; + } + JS_FreeValue(ctx, obj_proto); + } + + return proto; +} + +static JSValue js_cell_meme(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue proto = JS_NULL; + if (argc > 0 && !JS_IsNull(argv[0])) + proto = argv[0]; + + JSValue result = JS_NewObjectProto(ctx, proto); + if (JS_IsException(result)) + return result; + + /* Apply mixins */ + for (int i = 1; i < argc; i++) { + JSValue mix = argv[i]; + if (!JS_IsObject(mix) || JS_IsNull(mix) || JS_IsArray(ctx, mix)) + continue; + + JSPropertyEnum *tab; + uint32_t len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, mix, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + + for (uint32_t j = 0; j < len; j++) { + JSValue val = JS_GetProperty(ctx, mix, tab[j].atom); + if (JS_IsException(val)) { + for (uint32_t k = j; k < len; k++) + JS_FreeAtom(ctx, tab[k].atom); + js_free(ctx, tab); + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + JS_SetProperty(ctx, result, tab[j].atom, val); + JS_FreeAtom(ctx, tab[j].atom); + } + js_free(ctx, tab); + } + + return result; +} + +/* ============================================================================ + * splat() function - flatten object with prototype chain + * ============================================================================ */ + +static JSValue js_cell_splat(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_NULL; + + JSValue obj = argv[0]; + if (!JS_IsObject(obj) || JS_IsNull(obj)) + return JS_NULL; + + JSValue result = JS_NewObject(ctx); + if (JS_IsException(result)) + return JS_EXCEPTION; + + JSValue current = JS_DupValue(ctx, obj); + + /* Walk prototype chain and collect text keys */ + while (!JS_IsNull(current)) { + JSPropertyEnum *tab; + uint32_t len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, current, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { + JS_FreeValue(ctx, current); + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + + for (uint32_t i = 0; i < len; i++) { + JSAtom atom = tab[i].atom; + /* Check if property not already in result */ + int has = JS_HasProperty(ctx, result, atom); + if (has < 0) { + for (uint32_t j = i; j < len; j++) + JS_FreeAtom(ctx, tab[j].atom); + js_free(ctx, tab); + JS_FreeValue(ctx, current); + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + if (!has) { + JSValue val = JS_GetProperty(ctx, current, atom); + if (JS_IsException(val)) { + for (uint32_t j = i; j < len; j++) + JS_FreeAtom(ctx, tab[j].atom); + js_free(ctx, tab); + JS_FreeValue(ctx, current); + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + /* Only include serializable types */ + int tag = JS_VALUE_GET_TAG(val); + if (JS_IsObject(val) || JS_IsNumber(val) || + tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE || + tag == JS_TAG_BOOL) { + JS_SetProperty(ctx, result, atom, val); + } else { + JS_FreeValue(ctx, val); + } + } + JS_FreeAtom(ctx, tab[i].atom); + } + js_free(ctx, tab); + + JSValue next = JS_GetPrototype(ctx, current); + JS_FreeValue(ctx, current); + current = next; + } + + /* Call to_data if present */ + JSValue to_data = JS_GetPropertyStr(ctx, obj, "to_data"); + if (JS_IsFunction(ctx, to_data)) { + JSValue args[1] = { result }; + JSValue extra = JS_Call(ctx, to_data, obj, 1, args); + if (!JS_IsException(extra) && JS_IsObject(extra)) { + JSPropertyEnum *tab; + uint32_t len; + if (JS_GetOwnPropertyNames(ctx, &tab, &len, extra, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) >= 0) { + for (uint32_t i = 0; i < len; i++) { + JSValue val = JS_GetProperty(ctx, extra, tab[i].atom); + JS_SetProperty(ctx, result, tab[i].atom, val); + JS_FreeAtom(ctx, tab[i].atom); + } + js_free(ctx, tab); + } + } + JS_FreeValue(ctx, extra); + } + JS_FreeValue(ctx, to_data); + + return result; +} + /* ============================================================================ * length() function * ============================================================================ */ @@ -40291,20 +40526,6 @@ static JSValue js_cell_is_proto(JSContext *ctx, JSValueConst this_val, return JS_FALSE; } -/* ============================================================================ - * Cell Script Global Function Registration - * ============================================================================ */ - -static const JSCFunctionListEntry js_global_funcs[] = { - JS_CFUNC_DEF("parseInt", 2, js_parseInt ), - JS_CFUNC_DEF("parseFloat", 1, js_parseFloat ), - JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ), - JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ), - JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), - JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), - JS_PROP_STRING_DEF("[Symbol.toStringTag]", "global", JS_PROP_CONFIGURABLE ), -}; - /* eval */ void JS_AddIntrinsicEval(JSContext *ctx) @@ -40474,12 +40695,6 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) js_array_iterator_proto_funcs, countof(js_array_iterator_proto_funcs)); - /* parseFloat and parseInteger must be defined before Number - because of the Number.parseFloat and Number.parseInteger - aliases */ - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs, - countof(js_global_funcs)); - /* String */ ctx->class_proto[JS_CLASS_STRING] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_STRING); @@ -40496,10 +40711,6 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) js_string_iterator_proto_funcs, countof(js_string_iterator_proto_funcs)); - /* Math: create as autoinit object */ - js_random_init(ctx); - JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj)); - /* ES6 Symbol */ ctx->class_proto[JS_CLASS_SYMBOL] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SYMBOL], js_symbol_proto_funcs, @@ -40518,11 +40729,6 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) } /* global properties */ - ctx->eval_obj = JS_NewCFunction(ctx, js_global_eval, "eval", 1); - JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_eval, - JS_DupValue(ctx, ctx->eval_obj), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis, JS_DupValue(ctx, ctx->global_obj), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); @@ -40620,6 +40826,36 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_proto", JS_NewCFunction(ctx, js_cell_is_proto, "is_proto", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* key() - create a unique symbol */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "key", + JS_NewCFunction(ctx, js_cell_key, "key", 0), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* reverse() - reverse an array */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "reverse", + JS_NewCFunction(ctx, js_cell_reverse, "reverse", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* proto() - get prototype of an object */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "proto", + JS_NewCFunction(ctx, js_cell_proto, "proto", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* splat() - flatten object with prototype chain */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "splat", + JS_NewCFunction(ctx, js_cell_splat, "splat", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* splat() - flatten object with prototype chain */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "meme", + JS_NewCFunction(ctx, js_cell_meme, "meme", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* pi - mathematical constant */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "pi", + JS_NewFloat64(ctx, 3.14159265358979323846264338327950288419716939937510), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } } @@ -41295,6 +41531,404 @@ void *js_debugger_val_address(JSContext *ctx, JSValue val) { return JS_VALUE_GET_PTR(val); } +/* ============================================================================ + * Cell Script Module: json + * Provides json.encode() and json.decode() using pure C implementation + * ============================================================================ */ + +static JSValue js_cell_json_encode(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_ThrowTypeError(ctx, "json.encode requires at least 1 argument"); + + JSValue replacer = argc > 1 ? argv[1] : JS_NULL; + JSValue space = argc > 2 ? argv[2] : JS_NewInt32(ctx, 1); + JSValue result = JS_JSONStringify(ctx, argv[0], replacer, space); + if (argc <= 2) + JS_FreeValue(ctx, space); + return result; +} + +static JSValue js_cell_json_decode(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_ThrowTypeError(ctx, "json.decode requires at least 1 argument"); + + int tag = JS_VALUE_GET_TAG(argv[0]); + if (tag != JS_TAG_STRING && tag != JS_TAG_STRING_ROPE) { + JSValue err = JS_NewError(ctx); + JS_DefinePropertyValueStr(ctx, err, "message", + JS_NewString(ctx, "couldn't parse text: not a string"), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + return JS_Throw(ctx, err); + } + + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) + return JS_EXCEPTION; + + size_t len = strlen(str); + JSValue result = JS_ParseJSON(ctx, str, len, ""); + JS_FreeCString(ctx, str); + + /* Apply reviver if provided */ + if (argc > 1 && JS_IsFunction(ctx, argv[1]) && !JS_IsException(result)) { + /* Create wrapper object to pass to reviver */ + JSValue wrapper = JS_NewObject(ctx); + JS_DefinePropertyValueStr(ctx, wrapper, "", result, JS_PROP_C_W_E); + + JSValue holder = wrapper; + JSAtom key = JS_NewAtom(ctx, ""); + JSValue args[2] = { JS_AtomToString(ctx, key), JS_GetProperty(ctx, holder, key) }; + JSValue final = JS_Call(ctx, argv[1], holder, 2, args); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + JS_FreeAtom(ctx, key); + JS_FreeValue(ctx, wrapper); + result = final; + } + + return result; +} + +static const JSCFunctionListEntry js_cell_json_funcs[] = { + JS_CFUNC_DEF("encode", 1, js_cell_json_encode), + JS_CFUNC_DEF("decode", 1, js_cell_json_decode), +}; + +JSValue js_json_use(JSContext *ctx) +{ + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, obj, js_cell_json_funcs, countof(js_cell_json_funcs)); + return obj; +} + +static JSValue js_math_e(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double power = 1.0; + if (argc > 0 && !JS_IsNull(argv[0])) { + if (JS_ToFloat64(ctx, &power, argv[0]) < 0) + return JS_EXCEPTION; + } + return JS_NewFloat64(ctx, exp(power)); +} + +/* ============================================================================ + * Cell Script Module: math/radians + * Provides trigonometric and math functions using radians + * ============================================================================ */ + +static JSValue js_math_rad_arc_cosine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, acos(x)); +} + +static JSValue js_math_rad_arc_sine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, asin(x)); +} + +static JSValue js_math_rad_arc_tangent(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, atan(x)); +} + +static JSValue js_math_rad_cosine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, cos(x)); +} + +static JSValue js_math_rad_sine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, sin(x)); +} + +static JSValue js_math_rad_tangent(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, tan(x)); +} + +static JSValue js_math_ln(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, log(x)); +} + +static JSValue js_math_log10(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, log10(x)); +} + +static JSValue js_math_log2(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, log2(x)); +} + +static JSValue js_math_power(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x, y; + if (argc < 2) + return JS_NULL; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + if (JS_ToFloat64(ctx, &y, argv[1]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, pow(x, y)); +} + +static JSValue js_math_root(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x, n; + if (argc < 2) + return JS_NULL; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + if (JS_ToFloat64(ctx, &n, argv[1]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, pow(x, 1.0 / n)); +} + +static JSValue js_math_sqrt(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, sqrt(x)); +} + +static const JSCFunctionListEntry js_math_radians_funcs[] = { + JS_CFUNC_DEF("arc_cosine", 1, js_math_rad_arc_cosine), + JS_CFUNC_DEF("arc_sine", 1, js_math_rad_arc_sine), + JS_CFUNC_DEF("arc_tangent", 1, js_math_rad_arc_tangent), + JS_CFUNC_DEF("cosine", 1, js_math_rad_cosine), + JS_CFUNC_DEF("sine", 1, js_math_rad_sine), + JS_CFUNC_DEF("tangent", 1, js_math_rad_tangent), + JS_CFUNC_DEF("ln", 1, js_math_ln), + JS_CFUNC_DEF("log", 1, js_math_log10), + JS_CFUNC_DEF("log2", 1, js_math_log2), + JS_CFUNC_DEF("power", 2, js_math_power), + JS_CFUNC_DEF("root", 2, js_math_root), + JS_CFUNC_DEF("sqrt", 1, js_math_sqrt), + JS_CFUNC_DEF("e", 1, js_math_e) +}; + +JSValue js_math_radians_use(JSContext *ctx) +{ + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, obj, js_math_radians_funcs, countof(js_math_radians_funcs)); + return obj; +} + +/* ============================================================================ + * Cell Script Module: math/degrees + * Provides trigonometric and math functions using degrees + * ============================================================================ */ + +#define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0) +#define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510) + +static JSValue js_math_deg_arc_cosine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, acos(x) * RAD2DEG); +} + +static JSValue js_math_deg_arc_sine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, asin(x) * RAD2DEG); +} + +static JSValue js_math_deg_arc_tangent(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, atan(x) * RAD2DEG); +} + +static JSValue js_math_deg_cosine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, cos(x * DEG2RAD)); +} + +static JSValue js_math_deg_sine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, sin(x * DEG2RAD)); +} + +static JSValue js_math_deg_tangent(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, tan(x * DEG2RAD)); +} + +static const JSCFunctionListEntry js_math_degrees_funcs[] = { + JS_CFUNC_DEF("arc_cosine", 1, js_math_deg_arc_cosine), + JS_CFUNC_DEF("arc_sine", 1, js_math_deg_arc_sine), + JS_CFUNC_DEF("arc_tangent", 1, js_math_deg_arc_tangent), + JS_CFUNC_DEF("cosine", 1, js_math_deg_cosine), + JS_CFUNC_DEF("sine", 1, js_math_deg_sine), + JS_CFUNC_DEF("tangent", 1, js_math_deg_tangent), + JS_CFUNC_DEF("ln", 1, js_math_ln), + JS_CFUNC_DEF("log", 1, js_math_log10), + JS_CFUNC_DEF("log2", 1, js_math_log2), + JS_CFUNC_DEF("power", 2, js_math_power), + JS_CFUNC_DEF("root", 2, js_math_root), + JS_CFUNC_DEF("sqrt", 1, js_math_sqrt), + JS_CFUNC_DEF("e", 1, js_math_e) +}; + +JSValue js_math_degrees_use(JSContext *ctx) +{ + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, obj, js_math_degrees_funcs, countof(js_math_degrees_funcs)); + return obj; +} + +/* ============================================================================ + * Cell Script Module: math/cycles + * Provides trigonometric and math functions using cycles (0-1 = full rotation) + * ============================================================================ */ + +#define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510) + +static JSValue js_math_cyc_arc_cosine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, acos(x) / TWOPI); +} + +static JSValue js_math_cyc_arc_sine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, asin(x) / TWOPI); +} + +static JSValue js_math_cyc_arc_tangent(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, atan(x) / TWOPI); +} + +static JSValue js_math_cyc_cosine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, cos(x * TWOPI)); +} + +static JSValue js_math_cyc_sine(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, sin(x * TWOPI)); +} + +static JSValue js_math_cyc_tangent(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + double x; + if (JS_ToFloat64(ctx, &x, argv[0]) < 0) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, tan(x * TWOPI)); +} + +static const JSCFunctionListEntry js_math_cycles_funcs[] = { + JS_CFUNC_DEF("arc_cosine", 1, js_math_cyc_arc_cosine), + JS_CFUNC_DEF("arc_sine", 1, js_math_cyc_arc_sine), + JS_CFUNC_DEF("arc_tangent", 1, js_math_cyc_arc_tangent), + JS_CFUNC_DEF("cosine", 1, js_math_cyc_cosine), + JS_CFUNC_DEF("sine", 1, js_math_cyc_sine), + JS_CFUNC_DEF("tangent", 1, js_math_cyc_tangent), + JS_CFUNC_DEF("ln", 1, js_math_ln), + JS_CFUNC_DEF("log", 1, js_math_log10), + JS_CFUNC_DEF("log2", 1, js_math_log2), + JS_CFUNC_DEF("power", 2, js_math_power), + JS_CFUNC_DEF("root", 2, js_math_root), + JS_CFUNC_DEF("sqrt", 1, js_math_sqrt), + JS_CFUNC_DEF("e", 1, js_math_e) +}; + +JSValue js_math_cycles_use(JSContext *ctx) +{ + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, obj, js_math_cycles_funcs, countof(js_math_cycles_funcs)); + return obj; +} + JSContext *JS_GetContext(JSRuntime *rt) { return rt->js; } diff --git a/test.ce b/test.ce index 9c1bb340..8b3f4415 100644 --- a/test.ce +++ b/test.ce @@ -3,7 +3,7 @@ var pkg = use('package') var fd = use('fd') var time = use('time') var json = use('json') -var utf8 = use('utf8') +var blob = use('blob') if (!args) args = [] @@ -628,7 +628,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed} } } ensure_dir(report_dir) - fd.slurpwrite(`${report_dir}/test.txt`, utf8.encode(txt_report)) + fd.slurpwrite(`${report_dir}/test.txt`, stone(new blob(txt_report))) log.console(`Report written to ${report_dir}/test.txt`) // Generate JSON per package @@ -645,7 +645,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed} } var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json` - fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_tests))) + fd.slurpwrite(json_path, stone(new blob(json.encode(pkg_tests)))) } } diff --git a/tests/utf8.cm b/tests/utf8.cm deleted file mode 100644 index 7e9f29c9..00000000 --- a/tests/utf8.cm +++ /dev/null @@ -1,34 +0,0 @@ -var blob = use('blob'); -var utf8 = use('utf8'); - -return { - test_blob_to_text: function() { - // Test blob to text conversion - var test_string = "Hello, δΈ–η•Œ! 🌍"; - var encoded_blob = utf8.encode(test_string); - var decoded_text = text(encoded_blob); - if (test_string != decoded_text) throw "Blob to text failed" - }, - - test_codepoints_to_text: function() { - // Test array of codepoints conversion - var test_string = "Hello, δΈ–η•Œ! 🌍"; - var codepoints = [72, 101, 108, 108, 111, 44, 32, 19990, 30028, 33, 32, 127757]; - var from_codepoints = text(codepoints); - if (from_codepoints != test_string) throw "Codepoints to text failed" - }, - - test_array_separator: function() { - // Test array with separator - var words = ["Hello", "world", "from", "text"]; - var joined = text(words, " "); - if (joined != "Hello world from text") throw "Array with separator failed" - }, - - test_mixed_array: function() { - // Test mixed array with codepoints - var mixed = [72, "ello", 32, "world"]; - var mixed_result = text(mixed, ""); - if (mixed_result != "Hello world") throw "Mixed array test failed" - }, -} diff --git a/utf8.c b/utf8.c deleted file mode 100644 index 1eee19f2..00000000 --- a/utf8.c +++ /dev/null @@ -1,210 +0,0 @@ -#include "cell.h" -#include -#include - -#include "kim.h" - -// Get codepoints from a UTF-8 string -JSC_CCALL(utf8_codepoints, - const char *str = JS_ToCString(js, argv[0]); - if (!str) return JS_EXCEPTION; - - JSValue arr = JS_NewArray(js); - int idx = 0; - - char *ptr = (char*)str; - while (*ptr) { - int codepoint = decode_utf8(&ptr); - JS_SetPropertyUint32(js, arr, idx++, JS_NewInt32(js, codepoint)); - } - - JS_FreeCString(js, str); - ret = arr; -) - -// Create UTF-8 string from codepoints -JSC_CCALL(utf8_from_codepoints, - int len = JS_ArrayLength(js, argv[0]); - - // Allocate buffer (worst case: 4 bytes per codepoint + null) - char *buffer = malloc(len * 4 + 1); - char *ptr = buffer; - - for (int i = 0; i < len; i++) { - JSValue val = JS_GetPropertyUint32(js, argv[0], i); - int codepoint; - JS_ToInt32(js, &codepoint, val); - JS_FreeValue(js, val); - - encode_utf8(&ptr, codepoint); - } - - *ptr = '\0'; - ret = JS_NewString(js, buffer); - free(buffer); -) - -// Count UTF-8 characters (runes) in a string -JSC_SCALL(utf8_length, - int count = utf8_count(str); - ret = JS_NewInt32(js, count); -) - -// Validate UTF-8 string -JSC_SCALL(utf8_validate, - char *ptr = (char*)str; - int valid = 1; - - while (*ptr) { - int start_pos = ptr - str; - int codepoint = decode_utf8(&ptr); - - // Check for invalid sequences - if (codepoint < 0 || codepoint > 0x10FFFF || - (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { - valid = 0; - break; - } - - // Check for overlong encodings - int bytes_used = ptr - (str + start_pos); - if ((codepoint <= 0x7F && bytes_used != 1) || - (codepoint <= 0x7FF && bytes_used != 2) || - (codepoint <= 0xFFFF && bytes_used != 3) || - (codepoint <= 0x10FFFF && bytes_used != 4)) { - valid = 0; - break; - } - } - - ret = JS_NewBool(js, valid); -) - -// Get byte length of UTF-8 string -JSC_SCALL(utf8_byte_length, - ret = JS_NewInt32(js, strlen(str)); -) - -// Encode string to UTF-8 bytes -JSC_SCALL(utf8_encode, - size_t len = strlen(str); - ret = js_new_blob_stoned_copy(js, str, len); -) - -// Decode UTF-8 bytes to string -JSC_CCALL(utf8_decode, - size_t len; - void *data = js_get_blob_data(js, &len, argv[0]); - if (data == (void*)-1) return JS_EXCEPTION; - if (!data || len == 0) return JS_ThrowTypeError(js, "No data present in blob"); - - // Create null-terminated string - char *str = malloc(len + 1); - memcpy(str, data, len); - str[len] = '\0'; - - ret = JS_NewString(js, str); - free(str); -) - -// Slice UTF-8 string by character indices (not byte indices) -JSC_CCALL(utf8_slice, - const char *str = JS_ToCString(js, argv[0]); - if (!str) return JS_EXCEPTION; - - int start = 0; - int end = utf8_count(str); - - if (argc > 1) JS_ToInt32(js, &start, argv[1]); - if (argc > 2) JS_ToInt32(js, &end, argv[2]); - - // Handle negative indices - int total = end; - if (start < 0) start = total + start; - if (end < 0) end = total + end; - - // Clamp values - if (start < 0) start = 0; - if (end > total) end = total; - if (start >= end) { - JS_FreeCString(js, str); - return JS_NewString(js, ""); - } - - // Find start position - char *ptr = (char*)str; - for (int i = 0; i < start && *ptr; i++) { - decode_utf8(&ptr); - } - char *start_ptr = ptr; - - // Find end position - for (int i = start; i < end && *ptr; i++) { - decode_utf8(&ptr); - } - - // Create substring - size_t slice_len = ptr - start_ptr; - char *slice = malloc(slice_len + 1); - memcpy(slice, start_ptr, slice_len); - slice[slice_len] = '\0'; - - ret = JS_NewString(js, slice); - free(slice); - JS_FreeCString(js, str); -) - -// Get character at index -JSC_CCALL(utf8_char_at, - const char *str = JS_ToCString(js, argv[0]); - if (!str) return JS_EXCEPTION; - - int index; - JS_ToInt32(js, &index, argv[1]); - - char *ptr = (char*)str; - int count = 0; - - // Skip to index - while (*ptr && count < index) { - decode_utf8(&ptr); - count++; - } - - if (!*ptr || count != index) { - JS_FreeCString(js, str); - return JS_NULL; - } - - // Get the character - char *char_start = ptr; - decode_utf8(&ptr); - - size_t char_len = ptr - char_start; - char *result = malloc(char_len + 1); - memcpy(result, char_start, char_len); - result[char_len] = '\0'; - - ret = JS_NewString(js, result); - free(result); - JS_FreeCString(js, str); -) - -static const JSCFunctionListEntry js_utf8_funcs[] = { - MIST_FUNC_DEF(utf8, codepoints, 1), - MIST_FUNC_DEF(utf8, from_codepoints, 1), - MIST_FUNC_DEF(utf8, length, 1), - MIST_FUNC_DEF(utf8, validate, 1), - MIST_FUNC_DEF(utf8, byte_length, 1), - MIST_FUNC_DEF(utf8, encode, 1), - MIST_FUNC_DEF(utf8, decode, 1), - MIST_FUNC_DEF(utf8, slice, 3), - MIST_FUNC_DEF(utf8, char_at, 2), -}; - -JSValue js_utf8_use(JSContext *js) -{ - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_utf8_funcs, countof(js_utf8_funcs)); - return mod; -} \ No newline at end of file