diff --git a/bench.ce b/bench.ce index 6ef077bb..6cef42e5 100644 --- a/bench.ce +++ b/bench.ce @@ -25,8 +25,8 @@ def MAX_BATCH_SIZE = 100000000 // 100M iterations max per batch // Statistical functions function median(arr) { if (arr.length == 0) return 0 - var sorted = arr.slice().sort(function(a, b) { return a - b }) - var mid = number.floor(arr.length / 2) + var sorted = sort(arr) + var mid = floor(arr.length / 2) if (arr.length % 2 == 0) { return (sorted[mid - 1] + sorted[mid]) / 2 } @@ -54,8 +54,8 @@ function stddev(arr, mean_val) { function percentile(arr, p) { if (arr.length == 0) return 0 - var sorted = arr.slice().sort(function(a, b) { return a - b }) - var idx = number.floor(arr.length * p / 100) + var sorted = sort(arr) + var idx = floor(arr.length * p / 100) if (idx >= arr.length) idx = arr.length - 1 return sorted[idx] } @@ -228,7 +228,7 @@ function calibrate_batch_size(bench_fn, is_batch) { if (dt > 0 && dt < TARGET_SAMPLE_NS && is_number(n) && is_number(dt)) { var calc = n * TARGET_SAMPLE_NS / dt if (is_number(calc) && calc > 0) { - var target_n = number.floor(calc) + var target_n = floor(calc) // Check if floor returned a valid number if (is_number(target_n) && target_n > 0) { if (target_n > MAX_BATCH_SIZE) target_n = MAX_BATCH_SIZE @@ -376,20 +376,20 @@ function run_single_bench(bench_fn, bench_name) { // Calculate ops/s from median var ops_per_sec = 0 if (median_ns > 0) { - ops_per_sec = number.floor(1000000000 / median_ns) + ops_per_sec = floor(1000000000 / median_ns) } return { name: bench_name, batch_size: batch_size, samples: SAMPLES, - mean_ns: number.round(mean_ns), - median_ns: number.round(median_ns), - min_ns: number.round(min_ns), - max_ns: number.round(max_ns), - stddev_ns: number.round(stddev_ns), - p95_ns: number.round(p95_ns), - p99_ns: number.round(p99_ns), + mean_ns: round(mean_ns), + median_ns: round(median_ns), + min_ns: round(min_ns), + max_ns: round(max_ns), + stddev_ns: round(stddev_ns), + p95_ns: round(p95_ns), + p99_ns: round(p99_ns), ops_per_sec: ops_per_sec } } @@ -397,17 +397,17 @@ function run_single_bench(bench_fn, bench_name) { // Format nanoseconds for display function format_ns(ns) { if (ns < 1000) return `${ns}ns` - if (ns < 1000000) return `${number.round(ns / 1000 * 100) / 100}µs` - if (ns < 1000000000) return `${number.round(ns / 1000000 * 100) / 100}ms` - return `${number.round(ns / 1000000000 * 100) / 100}s` + if (ns < 1000000) return `${round(ns / 1000 * 100) / 100}µs` + if (ns < 1000000000) return `${round(ns / 1000000 * 100) / 100}ms` + return `${round(ns / 1000000000 * 100) / 100}s` } // Format ops/sec for display function format_ops(ops) { if (ops < 1000) return `${ops} ops/s` - if (ops < 1000000) return `${number.round(ops / 1000 * 100) / 100}K ops/s` - if (ops < 1000000000) return `${number.round(ops / 1000000 * 100) / 100}M ops/s` - return `${number.round(ops / 1000000000 * 100) / 100}G ops/s` + if (ops < 1000000) return `${round(ops / 1000 * 100) / 100}K ops/s` + if (ops < 1000000000) return `${round(ops / 1000000 * 100) / 100}M ops/s` + return `${round(ops / 1000000000 * 100) / 100}G ops/s` } // Run benchmarks for a package @@ -525,7 +525,7 @@ log.console(`Benchmarks: ${total_benches} total`) // Generate reports function generate_reports() { - var timestamp = text(number.floor(time.number())) + var timestamp = text(floor(time.number())) var report_dir = shop.get_reports_dir() + '/bench_' + timestamp testlib.ensure_dir(report_dir) diff --git a/benchmarks/binarytree.ce b/benchmarks/binarytree.ce index c317a2e3..aa6d7877 100644 --- a/benchmarks/binarytree.ce +++ b/benchmarks/binarytree.ce @@ -1,5 +1,5 @@ function mainThread() { - var maxDepth = number.max(6, Number(arg[0] || 16)); + var maxDepth = max(6, Number(arg[0] || 16)); var stretchDepth = maxDepth + 1; var check = itemCheck(bottomUpTree(stretchDepth)); diff --git a/benchmarks/eratosthenes.ce b/benchmarks/eratosthenes.ce index 025bbb94..408e35f7 100644 --- a/benchmarks/eratosthenes.ce +++ b/benchmarks/eratosthenes.ce @@ -3,7 +3,7 @@ var math = use('math/radians') function eratosthenes (n) { var sieve = blob(n, true) - var sqrtN = number.whole(math.sqrt(n)); + var sqrtN = whole(math.sqrt(n)); for (i = 2; i <= sqrtN; i++) if (sieve.read_logical(i)) diff --git a/benchmarks/js_perf.ce b/benchmarks/js_perf.ce index 4db1e304..aa12c60a 100644 --- a/benchmarks/js_perf.ce +++ b/benchmarks/js_perf.ce @@ -204,7 +204,7 @@ function benchStringOps() { var joinTime = measureTime(function() { for (var i = 0; i < iterations.complex; i++) { - var result = strings.join(","); + var result = text(strings, ","); } }); @@ -239,7 +239,7 @@ function benchArithmetic() { var result = 1.5; for (var i = 0; i < iterations.simple; i++) { result = math.sine(result) + math.cosine(i * 0.01); - result = math.sqrt(number.abs(result)) + 0.1; + result = math.sqrt(abs(result)) + 0.1; } }); diff --git a/benchmarks/nota.ce b/benchmarks/nota.ce index c2aed58c..39fb42b7 100644 --- a/benchmarks/nota.ce +++ b/benchmarks/nota.ce @@ -44,8 +44,8 @@ for (var i = 0; i < 100; i++) { // Calculate statistics function getStats(arr) { def avg = arr.reduce((a, b) => a + b) / arr.length; - def min = number.min(...arr); - def max = number.max(...arr); + def min = min(...arr); + def max = max(...arr); return { avg, min, max }; } diff --git a/benchmarks/wota.ce b/benchmarks/wota.ce index 14aad1f6..79b0936f 100644 --- a/benchmarks/wota.ce +++ b/benchmarks/wota.ce @@ -63,7 +63,7 @@ def benchmarks = [ { name: "Large Array (1k numbers)", // A thousand random numbers - data: [ Array.from({length:1000}, (_, i) => i * 0.5) ], + data: [ array(1000, i => i *0.5) ], iterations: 1000 }, { diff --git a/benchmarks/wota_nota_json.ce b/benchmarks/wota_nota_json.ce index f9ef5bbb..c4f750c8 100644 --- a/benchmarks/wota_nota_json.ce +++ b/benchmarks/wota_nota_json.ce @@ -98,7 +98,7 @@ def benchmarks = [ }, { name: "large_array", - data: [ Array.from({length:1000}, (_, i) => i) ], + data: [ array(1000, i => i) ], iterations: 1000 }, ]; @@ -171,13 +171,13 @@ var bench = benchmarks.find(b => b.name == scenario_name); if (!lib) { log.console('Unknown library:', lib_name); - log.console('Available libraries:', libraries.map(l => l.name).join(', ')); + log.console('Available libraries:', text(libraries.map(l => l.name), ', ')); $stop() } if (!bench) { log.console('Unknown scenario:', scenario_name); - log.console('Available scenarios:', benchmarks.map(b => b.name).join(', ')); + log.console('Available scenarios:', text(benchmarks.map(b => b.name), ', ')); $stop() } diff --git a/build.ce b/build.ce index 08aa9f89..5c92889b 100644 --- a/build.ce +++ b/build.ce @@ -80,7 +80,7 @@ if (!target) { if (target && !build.has_target(target)) { log.error('Invalid target: ' + target) - log.console('Available targets: ' + build.list_targets().join(', ')) + log.console('Available targets: ' + text(build.list_targets(), ', ')) $stop() } diff --git a/build.cm b/build.cm index c86efd75..5416def4 100644 --- a/build.cm +++ b/build.cm @@ -197,7 +197,7 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude // Compute link key from all inputs that affect the dylib output function compute_link_key(objects, ldflags, target_ldflags, target, cc) { // Sort objects for deterministic hash - var sorted_objects = objects.slice().sort() + var sorted_objects = sort(objects) // Build a string representing all link inputs var parts = [] @@ -214,7 +214,7 @@ function compute_link_key(objects, ldflags, target_ldflags, target, cc) { parts.push('target_ldflag:' + target_ldflags[i]) } - return content_hash(parts.join('\n')) + return content_hash(text(parts, '\n')) } // Build a dynamic library for a package @@ -372,7 +372,7 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out var pkg_dir = shop.get_package_dir(pkg) // Deduplicate based on the entire LDFLAGS string for this package - var ldflags_key = pkg + ':' + ldflags.join(' ') + var ldflags_key = pkg + ':' + text(ldflags, ' ') if (!seen_flags[ldflags_key]) { seen_flags[ldflags_key] = true for (var j = 0; j < ldflags.length; j++) { diff --git a/cellfs.cm b/cellfs.cm index a43eed70..e3ab894c 100644 --- a/cellfs.cm +++ b/cellfs.cm @@ -268,7 +268,7 @@ function stat(path) { // Get search paths function searchpath() { - return mounts.slice() + return array(mounts) } // Mount a package using the shop system diff --git a/clone.ce b/clone.ce index e87ca9ff..746206e3 100644 --- a/clone.ce +++ b/clone.ce @@ -97,7 +97,7 @@ try { // Skip the first directory (repo-commit prefix) parts.shift() - var rel_path = parts.join('/') + var rel_path = text(parts, '/') var full_path = target_path + '/' + rel_path var dir_path = full_path.substring(0, full_path.lastIndexOf('/')) diff --git a/config.ce b/config.ce index 101b8fe4..ca67c73e 100644 --- a/config.ce +++ b/config.ce @@ -162,7 +162,7 @@ switch (command) { 'reply_timeout', 'actor_max', 'stack_max' ] if (!valid_system_keys.includes(path[1])) { - log.error("Invalid system key. Valid keys: " + valid_system_keys.join(', ')) + log.error("Invalid system key. Valid keys: " + text(valid_system_keys, ', ')) $stop() return } diff --git a/docs/index.md b/docs/index.md index a56857da..78a7e2b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,7 +35,7 @@ cell hello ## Standard Library - [text](library/text.md) — string manipulation -- [number](library/number.md) — numeric operations +- [number](library/number.md) — numeric operations (functions are global: `floor()`, `max()`, etc.) - [array](library/array.md) — array utilities - [object](library/object.md) — object utilities - [blob](library/blob.md) — binary data diff --git a/docs/library/number.md b/docs/library/number.md index c0a30826..dcdcc319 100644 --- a/docs/library/number.md +++ b/docs/library/number.md @@ -46,98 +46,98 @@ number("0xff", "j") // 255 ## Methods -### number.abs(n) +### abs(n) Absolute value. ```javascript -number.abs(-5) // 5 -number.abs(5) // 5 +abs(-5) // 5 +abs(5) // 5 ``` -### number.sign(n) +### sign(n) Returns -1, 0, or 1. ```javascript -number.sign(-5) // -1 -number.sign(0) // 0 -number.sign(5) // 1 +sign(-5) // -1 +sign(0) // 0 +sign(5) // 1 ``` -### number.floor(n, place) +### floor(n, place) Round down. ```javascript -number.floor(4.9) // 4 -number.floor(4.567, 2) // 4.56 +floor(4.9) // 4 +floor(4.567, 2) // 4.56 ``` -### number.ceiling(n, place) +### ceiling(n, place) Round up. ```javascript -number.ceiling(4.1) // 5 -number.ceiling(4.123, 2) // 4.13 +ceiling(4.1) // 5 +ceiling(4.123, 2) // 4.13 ``` -### number.round(n, place) +### round(n, place) Round to nearest. ```javascript -number.round(4.5) // 5 -number.round(4.567, 2) // 4.57 +round(4.5) // 5 +round(4.567, 2) // 4.57 ``` -### number.trunc(n, place) +### trunc(n, place) Truncate toward zero. ```javascript -number.trunc(4.9) // 4 -number.trunc(-4.9) // -4 +trunc(4.9) // 4 +trunc(-4.9) // -4 ``` -### number.whole(n) +### whole(n) Get the integer part. ```javascript -number.whole(4.9) // 4 -number.whole(-4.9) // -4 +whole(4.9) // 4 +whole(-4.9) // -4 ``` -### number.fraction(n) +### fraction(n) Get the fractional part. ```javascript -number.fraction(4.75) // 0.75 +fraction(4.75) // 0.75 ``` -### number.min(...values) +### min(...values) Return the smallest value. ```javascript -number.min(3, 1, 4, 1, 5) // 1 +min(3, 1, 4, 1, 5) // 1 ``` -### number.max(...values) +### max(...values) Return the largest value. ```javascript -number.max(3, 1, 4, 1, 5) // 5 +max(3, 1, 4, 1, 5) // 5 ``` -### number.remainder(dividend, divisor) +### remainder(dividend, divisor) Compute remainder. ```javascript -number.remainder(17, 5) // 2 +remainder(17, 5) // 2 ``` diff --git a/fetch.ce b/fetch.ce index ce33eefe..94ab7a7d 100644 --- a/fetch.ce +++ b/fetch.ce @@ -84,6 +84,6 @@ if (downloaded_count > 0) parts.push(`${text(downloaded_count)} downloaded`) if (cached_count > 0) parts.push(`${text(cached_count)} cached`) if (fail_count > 0) parts.push(`${text(fail_count)} failed`) if (parts.length == 0) parts.push("nothing to fetch") -log.console("Fetch complete: " + parts.join(", ")) +log.console("Fetch complete: " + text(parts, ", ")) $stop() diff --git a/internal/engine.cm b/internal/engine.cm index f8971208..f06053c2 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -118,7 +118,7 @@ function caller_data(depth = 0) } function console_rec(line, file, msg) { - return `[${_cell.id.slice(0,5)}] [${file}:${line}]: ${msg}\n` + return `[${text(_cell.id, 0, 5)}] [${file}:${line}]: ${msg}\n` // time: [${time.text("mb d yyyy h:nn:ss")}] } diff --git a/internal/shop.cm b/internal/shop.cm index 136c4596..497915d5 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -115,8 +115,8 @@ function split_explicit_package_import(path) // Find the longest prefix that is an installed package for (var i = parts.length - 1; i >= 1; i--) { - var pkg_candidate = parts.slice(0, i).join('/') - var mod_path = parts.slice(i).join('/') + var pkg_candidate = text(array(parts, 0, i), '/') + var mod_path = text(array(parts, i), '/') if (!mod_path || mod_path.length == 0) continue var candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate) @@ -220,7 +220,7 @@ function get_import_name(path) { var parts = path.split('/') if (parts.length < 2) return null - return parts.slice(1).join('/') + return text(array(parts, 1), '/') } // Given a path like 'prosperon/sprite' and a package context, @@ -383,7 +383,7 @@ Shop.get_script_capabilities = function(path) { function inject_params(inject) { if (!inject || !inject.length) return '' - return ', ' + inject.join(', ') + return ', ' + text(inject, ', ') } function inject_values(inject) { @@ -707,7 +707,7 @@ function resolve_module_info(path, package_context) { var c_resolve = resolve_c_symbol(path, package_context) || {scope:999} var mod_resolve = resolve_locator(path + '.cm', package_context) || {scope:999} - var min_scope = number.min(c_resolve.scope, mod_resolve.scope) + var min_scope = min(c_resolve.scope, mod_resolve.scope) if (min_scope == 999) return null @@ -1104,7 +1104,7 @@ function install_zip(zip_blob, target_dir) { if (parts.length <= 1) continue parts.shift() - var rel_path = parts.join('/') + var rel_path = text(parts, '/') var full_path = target_dir + '/' + rel_path var dir_path = full_path.substring(0, full_path.lastIndexOf('/')) diff --git a/list.ce b/list.ce index 1190d177..ce8499d2 100644 --- a/list.ce +++ b/list.ce @@ -94,7 +94,7 @@ function print_deps(ctx, indent) { } if (status.length > 0) { - line += " [" + status.join(", ") + "]" + line += " [" + text(status, ", ") + "]" } log.console(line) diff --git a/pack.ce b/pack.ce index 17e616e2..9af2c260 100644 --- a/pack.ce +++ b/pack.ce @@ -22,7 +22,7 @@ if (args.length < 1) { log.error(' -t, --target Cross-compile for target platform') log.error(' -b, --buildtype Build type: release, debug, minsize (default: release)') log.error('') - log.error('Available targets: ' + build.list_targets().join(', ')) + log.error('Available targets: ' + text(build.list_targets(), ', ')) $stop() return } @@ -63,7 +63,7 @@ for (var i = 1; i < args.length; i++) { log.console(' -t, --target Cross-compile for target platform') log.console(' -b, --buildtype Build type: release, debug, minsize (default: release)') log.console('') - log.console('Available targets: ' + build.list_targets().join(', ')) + log.console('Available targets: ' + text(build.list_targets(), ', ')) $stop() } else { log.error('Unknown option: ' + args[i]) @@ -109,7 +109,7 @@ for (var package of packages) { shop.extract(package) } -log.console('Building static binary from ' + text(packages.length) + ' packages: ' + packages.join(', ')) +log.console('Building static binary from ' + text(packages.length) + ' packages: ' + text(packages, ', ')) try { var result = build.build_static(packages, target, output_name, buildtype) diff --git a/package.cm b/package.cm index 7ca1d7f8..e8b4901e 100644 --- a/package.cm +++ b/package.cm @@ -6,9 +6,6 @@ var json = use('json') var os = use('os') var link = use('link') -// Cache for loaded configs to avoid toml re-parsing corruption -var config_cache = {} - // Convert package name to a safe directory name // For absolute paths (local packages), replace / with _ // For remote packages, keep slashes as they use nested directories @@ -47,10 +44,6 @@ package.load_config = function(name) { var config_path = get_path(name) + '/cell.toml' - // Return cached config if available - if (config_cache[config_path]) - return config_cache[config_path] - if (!fd.is_file(config_path)) throw Error(`${config_path} does not exist`) @@ -63,10 +56,6 @@ package.load_config = function(name) return {} } - // Deep copy to avoid toml module's shared state bug and cache it - result = json.decode(json.encode(result)) - config_cache[config_path] = result - return result } @@ -169,7 +158,7 @@ package.split_alias = function(name, path) var deps = config.dependencies if (deps && deps[first_part]) { var dep_locator = deps[first_part] - var remaining_path = parts.slice(1).join('/') + var remaining_path = text(array(parts, 1), '/') return { package: dep_locator, path: remaining_path } } } catch (e) { diff --git a/pronto.cm b/pronto.cm index 8ac7439b..0a3a8694 100644 --- a/pronto.cm +++ b/pronto.cm @@ -153,7 +153,7 @@ function parallel(requestor_array, throttle, need) { } } - def concurrent = throttle ? number.min(throttle, length) : length + def concurrent = throttle ? min(throttle, length) : length for (var i = 0; i < concurrent; i++) start_one() return cancel @@ -238,7 +238,7 @@ function race(requestor_array, throttle, need) { } } - def concurrent = throttle ? number.min(throttle, length) : length + def concurrent = throttle ? min(throttle, length) : length for (var i = 0; i < concurrent; i++) start_one() return cancel diff --git a/random.cm b/random.cm index 18c46f75..1b7b5e7a 100644 --- a/random.cm +++ b/random.cm @@ -14,7 +14,7 @@ rnd.random_fit = function() rnd.random_whole = function(num) { - return number.floor(rnd.random() * num) + return floor(rnd.random() * num) } rnd.random_range = function(min,max) diff --git a/resolve.ce b/resolve.ce index 08fa752c..5df828bc 100644 --- a/resolve.ce +++ b/resolve.ce @@ -170,7 +170,7 @@ for (var i = 0; i < sorted.length; i++) { } if (status_parts.length > 0) { - line += " [" + status_parts.join(", ") + "]" + line += " [" + text(status_parts, ", ") + "]" } log.console(line) @@ -183,10 +183,10 @@ for (var i = 0; i < sorted.length; i++) { if (cflags.length > 0 || ldflags.length > 0) { log.console(indent + " Compilation inputs:") if (cflags.length > 0) { - log.console(indent + " CFLAGS: " + cflags.join(' ')) + log.console(indent + " CFLAGS: " + text(cflags, ' ')) } if (ldflags.length > 0) { - log.console(indent + " LDFLAGS: " + ldflags.join(' ')) + log.console(indent + " LDFLAGS: " + text(ldflags, ' ')) } } } catch (e) { diff --git a/source/quickjs.c b/source/quickjs.c index 4b04405a..a6101539 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -29533,170 +29533,6 @@ fail: return JS_EXCEPTION; } -static JSValue js_array_from(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - // from(items, mapfn = void 0, this_arg = void 0) - JSValueConst items = argv[0], mapfn, this_arg; - JSValueConst args[2]; - JSValue iter, r, v, v2, arrayLike, next_method, enum_obj; - int64_t k, len; - int done, mapping; - - mapping = FALSE; - mapfn = JS_NULL; - this_arg = JS_NULL; - r = JS_NULL; - arrayLike = JS_NULL; - iter = JS_NULL; - enum_obj = JS_NULL; - next_method = JS_NULL; - - if (argc > 1) { - mapfn = argv[1]; - if (!JS_IsNull(mapfn)) { - if (check_function(ctx, mapfn)) - goto exception; - mapping = 1; - if (argc > 2) - this_arg = argv[2]; - } - } - iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); - if (JS_IsException(iter)) - goto exception; - if (!JS_IsNull(iter) && !JS_IsNull(iter)) { - if (!JS_IsFunction(ctx, iter)) { - JS_ThrowTypeError(ctx, "value is not iterable"); - goto exception; - } - if (JS_IsConstructor(ctx, this_val)) - r = JS_CallConstructor(ctx, this_val, 0, NULL); - else - r = JS_NewArray(ctx); - if (JS_IsException(r)) - goto exception; - enum_obj = JS_GetIterator2(ctx, items, iter); - if (JS_IsException(enum_obj)) - goto exception; - next_method = JS_GetProperty(ctx, enum_obj, JS_ATOM_next); - if (JS_IsException(next_method)) - goto exception; - for (k = 0;; k++) { - v = JS_IteratorNext(ctx, enum_obj, next_method, 0, NULL, &done); - if (JS_IsException(v)) - goto exception; - if (done) - break; - if (mapping) { - args[0] = v; - args[1] = JS_NewInt32(ctx, k); - v2 = JS_Call(ctx, mapfn, this_arg, 2, args); - JS_FreeValue(ctx, v); - v = v2; - if (JS_IsException(v)) - goto exception_close; - } - if (JS_DefinePropertyValueInt64(ctx, r, k, v, - JS_PROP_C_W_E | JS_PROP_THROW) < 0) - goto exception_close; - } - } else { - arrayLike = JS_ToObject(ctx, items); - if (JS_IsException(arrayLike)) - goto exception; - if (js_get_length64(ctx, &len, arrayLike) < 0) - goto exception; - v = JS_NewInt64(ctx, len); - args[0] = v; - if (JS_IsConstructor(ctx, this_val)) { - r = JS_CallConstructor(ctx, this_val, 1, args); - } else { - r = js_array_constructor(ctx, JS_NULL, 1, args); - } - JS_FreeValue(ctx, v); - if (JS_IsException(r)) - goto exception; - for(k = 0; k < len; k++) { - v = JS_GetPropertyInt64(ctx, arrayLike, k); - if (JS_IsException(v)) - goto exception; - if (mapping) { - args[0] = v; - args[1] = JS_NewInt32(ctx, k); - v2 = JS_Call(ctx, mapfn, this_arg, 2, args); - JS_FreeValue(ctx, v); - v = v2; - if (JS_IsException(v)) - goto exception; - } - if (JS_DefinePropertyValueInt64(ctx, r, k, v, - JS_PROP_C_W_E | JS_PROP_THROW) < 0) - goto exception; - } - } - if (JS_SetProperty(ctx, r, JS_ATOM_length, JS_NewUint32(ctx, k)) < 0) - goto exception; - goto done; - - exception_close: - JS_IteratorClose(ctx, enum_obj, TRUE); - exception: - JS_FreeValue(ctx, r); - r = JS_EXCEPTION; - done: - JS_FreeValue(ctx, arrayLike); - JS_FreeValue(ctx, iter); - JS_FreeValue(ctx, enum_obj); - JS_FreeValue(ctx, next_method); - return r; -} - -static JSValue js_array_of(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj, args[1]; - int i; - - if (JS_IsConstructor(ctx, this_val)) { - args[0] = JS_NewInt32(ctx, argc); - obj = JS_CallConstructor(ctx, this_val, 1, (JSValueConst *)args); - } else { - obj = JS_NewArray(ctx); - } - if (JS_IsException(obj)) - return JS_EXCEPTION; - for(i = 0; i < argc; i++) { - if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i]), - JS_PROP_THROW) < 0) { - goto fail; - } - } - if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, argc)) < 0) { - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - return obj; -} - -static JSValue js_array_isArray(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int ret; - ret = JS_IsArray(ctx, argv[0]); - if (ret < 0) - return JS_EXCEPTION; - else - return JS_NewBool(ctx, ret); -} - -static JSValue js_get_this(JSContext *ctx, - JSValueConst this_val) -{ - return JS_DupValue(ctx, this_val); -} - static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, JSValueConst len_val) { @@ -29743,13 +29579,6 @@ static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, } } -static const JSCFunctionListEntry js_array_funcs[] = { - JS_CFUNC_DEF("isArray", 1, js_array_isArray ), - JS_CFUNC_DEF("from", 1, js_array_from ), - JS_CFUNC_DEF("of", 0, js_array_of ), - JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), -}; - static int JS_isConcatSpreadable(JSContext *ctx, JSValueConst obj) { JSValue val; @@ -34587,7 +34416,6 @@ done: static const JSCFunctionListEntry js_regexp_funcs[] = { JS_CFUNC_DEF("escape", 1, js_regexp_escape ), - JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; static const JSCFunctionListEntry js_regexp_proto_funcs[] = { @@ -35779,20 +35607,6 @@ static JSValue js_cell_number_remainder(JSContext *ctx, JSValueConst this_val, return JS_NewFloat64(ctx, dividend - (trunc(dividend / divisor) * divisor)); } -static const JSCFunctionListEntry js_cell_number_funcs[] = { - JS_CFUNC_DEF("whole", 1, js_cell_number_whole), - JS_CFUNC_DEF("fraction", 1, js_cell_number_fraction), - JS_CFUNC_DEF("floor", 2, js_cell_number_floor), - JS_CFUNC_DEF("ceiling", 2, js_cell_number_ceiling), - JS_CFUNC_DEF("abs", 1, js_cell_number_abs), - JS_CFUNC_DEF("round", 2, js_cell_number_round), - JS_CFUNC_DEF("sign", 1, js_cell_number_sign), - JS_CFUNC_DEF("trunc", 2, js_cell_number_trunc), - JS_CFUNC_DEF("min", 0, js_cell_number_min), - JS_CFUNC_DEF("max", 0, js_cell_number_max), - JS_CFUNC_DEF("remainder", 2, js_cell_number_remainder), -}; - /* ---------------------------------------------------------------------------- * text function and sub-functions * ---------------------------------------------------------------------------- */ @@ -36093,22 +35907,34 @@ static JSValue js_cell_text(JSContext *ctx, JSValueConst this_val, if (argc == 1) return JS_DupValue(ctx, arg); - /* text(string, from, to) - substring */ - if (argc >= 3) { - int from, to; - JSString *p = JS_VALUE_GET_STRING(arg); - int len = p->len; + JSString *p = JS_VALUE_GET_STRING(arg); + int len = p->len; + /* text(string, from) - substring from index to end */ + /* text(string, from, to) - substring */ + if (argc >= 2 && (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT || + JS_VALUE_GET_TAG(argv[1]) == JS_TAG_FLOAT64)) { + int from; if (JS_ToInt32(ctx, &from, argv[1])) return JS_NULL; - if (JS_ToInt32(ctx, &to, argv[2])) - return JS_NULL; - /* Adjust negative indices */ + /* Adjust negative index */ if (from < 0) from += len; - if (to < 0) to += len; + if (from < 0) from = 0; + if (from > len) from = len; - if (from < 0 || from > to || to > len) + int to = len; /* Default: to end of string */ + if (argc >= 3) { + if (JS_ToInt32(ctx, &to, argv[2])) + return JS_NULL; + + /* Adjust negative index */ + if (to < 0) to += len; + if (to < 0) to = 0; + if (to > len) to = len; + } + + if (from > to) return JS_NULL; return js_sub_string(ctx, p, from, to); @@ -36644,15 +36470,6 @@ fail: return JS_EXCEPTION; } -static const JSCFunctionListEntry js_cell_text_funcs[] = { - JS_CFUNC_DEF("lower", 1, js_cell_text_lower), - JS_CFUNC_DEF("upper", 1, js_cell_text_upper), - JS_CFUNC_DEF("trim", 2, js_cell_text_trim), - JS_CFUNC_DEF("codepoint", 1, js_cell_text_codepoint), - JS_CFUNC_DEF("search", 3, js_cell_text_search), - JS_CFUNC_DEF("replace", 4, js_cell_text_replace), -}; - /* ---------------------------------------------------------------------------- * array function and sub-functions * ---------------------------------------------------------------------------- */ @@ -37407,14 +37224,6 @@ static JSValue js_cell_array_sort(JSContext *ctx, JSValueConst this_val, return result; } -static const JSCFunctionListEntry js_cell_array_funcs[] = { - JS_CFUNC_DEF("reduce", 4, js_cell_array_reduce), - JS_CFUNC_DEF("for", 4, js_cell_array_for), - JS_CFUNC_DEF("find", 4, js_cell_array_find), - JS_CFUNC_DEF("filter", 2, js_cell_array_filter), - JS_CFUNC_DEF("sort", 2, js_cell_array_sort), -}; - /* ---------------------------------------------------------------------------- * object function and sub-functions * ---------------------------------------------------------------------------- */ @@ -37680,10 +37489,6 @@ static JSValue js_cell_fn_apply(JSContext *ctx, JSValueConst this_val, return result; } -static const JSCFunctionListEntry js_cell_fn_funcs[] = { - JS_CFUNC_DEF("apply", 2, js_cell_fn_apply), -}; - /* ============================================================================ * Blob Intrinsic Type * ============================================================================ */ @@ -39008,8 +38813,6 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1, ctx->class_proto[JS_CLASS_ARRAY]); ctx->array_ctor = JS_DupValue(ctx, obj); - JS_SetPropertyFunctionList(ctx, obj, js_array_funcs, - countof(js_array_funcs)); /* XXX: create auto_initializer */ { @@ -39090,17 +38893,14 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) /* Cell Script global functions: text, number, array, object, fn */ { JSValue text_func = JS_NewCFunction(ctx, js_cell_text, "text", 2); - JS_SetPropertyFunctionList(ctx, text_func, js_cell_text_funcs, countof(js_cell_text_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "text", text_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue number_func = JS_NewCFunction(ctx, js_cell_number, "number", 2); - JS_SetPropertyFunctionList(ctx, number_func, js_cell_number_funcs, countof(js_cell_number_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "number", number_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue array_func = JS_NewCFunction(ctx, js_cell_array, "array", 4); - JS_SetPropertyFunctionList(ctx, array_func, js_cell_array_funcs, countof(js_cell_array_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "array", array_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); @@ -39110,7 +38910,6 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue fn_obj = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, fn_obj, js_cell_fn_funcs, countof(js_cell_fn_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "fn", fn_obj, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); @@ -39186,6 +38985,78 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_NewCFunction(ctx, js_cell_is_proto, "is_proto", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "apply", + JS_NewCFunction(ctx, js_cell_fn_apply, "apply", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "replace", + JS_NewCFunction(ctx, js_cell_text_replace, "replace", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "lower", + JS_NewCFunction(ctx, js_cell_text_lower, "lower", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "upper", + JS_NewCFunction(ctx, js_cell_text_upper, "upper", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "trim", + JS_NewCFunction(ctx, js_cell_text_trim, "trim", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "codepoint", + JS_NewCFunction(ctx, js_cell_text_codepoint, "codepoint", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "search", + JS_NewCFunction(ctx, js_cell_text_search, "search", 3), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "reduce", + JS_NewCFunction(ctx, js_cell_array_reduce, "reduce", 4), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "for", + JS_NewCFunction(ctx, js_cell_array_for, "for", 4), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "find", + JS_NewCFunction(ctx, js_cell_array_find, "find", 4), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "filter", + JS_NewCFunction(ctx, js_cell_array_filter, "filter", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "sort", + JS_NewCFunction(ctx, js_cell_array_sort, "sort", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* Number utility functions as globals */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "whole", + JS_NewCFunction(ctx, js_cell_number_whole, "whole", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "fraction", + JS_NewCFunction(ctx, js_cell_number_fraction, "fraction", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "floor", + JS_NewCFunction(ctx, js_cell_number_floor, "floor", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "ceiling", + JS_NewCFunction(ctx, js_cell_number_ceiling, "ceiling", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "abs", + JS_NewCFunction(ctx, js_cell_number_abs, "abs", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "round", + JS_NewCFunction(ctx, js_cell_number_round, "round", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "sign", + JS_NewCFunction(ctx, js_cell_number_sign, "sign", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "trunc", + JS_NewCFunction(ctx, js_cell_number_trunc, "trunc", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "min", + JS_NewCFunction(ctx, js_cell_number_min, "min", 0), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "max", + JS_NewCFunction(ctx, js_cell_number_max, "max", 0), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "remainder", + JS_NewCFunction(ctx, js_cell_number_remainder, "remainder", 2), + 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), diff --git a/test.ce b/test.ce index 4a2e7304..79e0e9a1 100644 --- a/test.ce +++ b/test.ce @@ -336,14 +336,14 @@ function run_tests(package_name, specific_test) { log.console(` FAIL ${t.name} ${test_entry.error.message}`) if (test_entry.error.stack) { - log.console(` ${test_entry.error.stack.split('\n').join('\n ')}`) + log.console(` ${text(test_entry.error.stack.split('\n'), '\n ')}`) } pkg_result.failed++ file_result.failed++ } var end_time = time.number() - test_entry.duration_ns = number.round((end_time - start_time) * 1000000000) + test_entry.duration_ns = round((end_time - start_time) * 1000000000) file_result.tests.push(test_entry) pkg_result.total++ @@ -415,7 +415,7 @@ function handle_actor_message(msg) { pending_actor_tests.splice(found_idx, 1) var end_time = time.number() - var duration_ns = number.round((end_time - base_entry.start_time) * 1000000000) + var duration_ns = round((end_time - base_entry.start_time) * 1000000000) var results = [] if (is_array(msg)) { @@ -567,7 +567,7 @@ if (all_actor_tests.length == 0) { // Generate Reports function function generate_reports(totals) { - var timestamp = text(number.floor(time.number())) + var timestamp = text(floor(time.number())) var report_dir = shop.get_reports_dir() + '/test_' + timestamp ensure_dir(report_dir) @@ -602,7 +602,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed} if (t.error) { txt_report += ` Message: ${t.error.message}\n` if (t.error.stack) { - txt_report += ` Stack:\n${t.error.stack.split('\n').map(function(l){return ` ${l}`}).join('\n')}\n` + txt_report += ` Stack:\n${text(t.error.stack.split('\n').map(function(l){return ` ${l}`}), '\n')}\n` } } txt_report += `\n` diff --git a/tests/nota.cm b/tests/nota.cm index a5c3c0ab..071bcb87 100644 --- a/tests/nota.cm +++ b/tests/nota.cm @@ -24,7 +24,7 @@ function deepCompare(expected, actual, path) { if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }; - var diff = number.abs(expected - actual); + var diff = abs(expected - actual); if (diff <= EPSILON) return { passed: true, messages: [] }; @@ -104,7 +104,7 @@ function makeTest(test) { var compareResult = deepCompare(expected, decoded); if (!compareResult.passed) { - throw compareResult.messages.join('; '); + throw text(compareResult.messages, '; '); } }; } diff --git a/tests/suite.cm b/tests/suite.cm index c3ba6830..2324777e 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -1556,7 +1556,7 @@ return { test_array_slice: function() { var arr = [1, 2, 3, 4, 5] - var sliced = arr.slice(1, 3) + var sliced = array(arr, 1, 3) if (length(sliced) != 2) throw "array slice length failed" if (sliced[0] != 2) throw "array slice first failed" if (sliced[1] != 3) throw "array slice second failed" @@ -1573,12 +1573,8 @@ return { test_array_join: function() { var arr = [1, 2, 3] - var caught = false - try { - var str = arr.join(",") - } catch (e) { - caught = true - } + var str = text(arr, ",") + if (str != "1,2,3") throw "array join with text() failed" }, test_array_indexOf: function() { @@ -1620,8 +1616,8 @@ return { test_string_slice: function() { var str = "hello" - if (str.slice(1, 4) != "ell") throw "string slice failed" - if (str.slice(-2) != "lo") throw "string slice negative failed" + if (text(str, 1, 4) != "ell") throw "string slice failed" + if (text(str, -2) != "lo") throw "string slice negative failed: " + text(str, -2) }, test_string_indexOf: function() { @@ -2043,8 +2039,8 @@ return { test_builtin_function_properties_still_work: function() { // Built-in functions like number, text, array should still have properties - var min_result = number.min(5, 3) - if (min_result != 3) throw "number.min should work" + var min_result = min(5, 3) + if (min_result != 3) throw "min should work" }, // ============================================================================ @@ -2108,7 +2104,7 @@ return { var proxy = function(name, args) { if (is_function(my_record[name])) { - return fn.apply(my_record[name], args) + return apply(my_record[name], args) } throw `unknown method: ${name}` } diff --git a/tests/wota.cm b/tests/wota.cm index 6f284498..d64f68cc 100644 --- a/tests/wota.cm +++ b/tests/wota.cm @@ -14,7 +14,7 @@ function deep_compare(expected, actual, path) { if (is_number(expected) && is_number(actual)) { if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] } - var diff = number.abs(expected - actual) + var diff = abs(expected - actual) if (diff <= EPSILON) return { passed: true, messages: [] } return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] } } @@ -115,7 +115,7 @@ function make_test(t) { var dec = wota.decode(enc) var cmp = deep_compare(t.input, dec) - if (!cmp.passed) throw cmp.messages.join('; ') + if (!cmp.passed) throw text(cmp.messages, '; ') } } diff --git a/time.cm b/time.cm index bb3a811f..11976d7b 100644 --- a/time.cm +++ b/time.cm @@ -47,9 +47,9 @@ time.isleap = function(y) { return time.yearsize(y) == 366; }; /* timecode utility */ time.timecode = function(t, fps = 24) { - var s = number.whole(t); + var s = whole(t); var frac = t - s; - return `${s}:${number.whole(frac * fps)}`; + return `${s}:${whole(frac * fps)}`; }; /* per-month day counts (non-leap) */ @@ -62,7 +62,7 @@ function time_record(num = now(), { if (is_object(num)) return num; - var monthdays = time.monthdays.slice(); + var monthdays = array(time.monthdays); var rec = { second : 0, minute : 0, hour : 0, yday : 0, year : 0, @@ -77,13 +77,13 @@ function time_record(num = now(), /* split into day + seconds-of-day */ var hms = num % time.day; - var day = number.floor(num / time.day); + var day = floor(num / time.day); if (hms < 0) { hms += time.day; day--; } rec.second = hms % time.minute; - var tmp = number.floor(hms / time.minute); + var tmp = floor(hms / time.minute); rec.minute = tmp % time.minute; - rec.hour = number.floor(tmp / time.minute); + rec.hour = floor(tmp / time.minute); rec.weekday = (day + 4_503_599_627_370_496 + 2) % 7; /* 2 → 1970-01-01 was Thursday */ /* year & day-of-year */ @@ -171,7 +171,7 @@ function time_text(num = now(), /* BCE/CE */ var year = rec.year > 0 ? rec.year : rec.year - 1; if (fmt.includes("c")) { - if (year < 0) { year = number.abs(year); fmt = fmt.replaceAll("c", "BC"); } + if (year < 0) { year = abs(year); fmt = fmt.replaceAll("c", "BC"); } else fmt = fmt.replaceAll("c", "AD"); } @@ -193,10 +193,10 @@ function time_text(num = now(), fmt = fmt.replaceAll(/mm[^bB]/g, rec.month + 1); fmt = fmt.replaceAll(/m[^bB]/g, rec.month + 1); fmt = fmt.replaceAll(/v[^bB]/g, rec.weekday); - fmt = fmt.replaceAll("mb", time.monthstr[rec.month].slice(0, 3)); + fmt = fmt.replaceAll("mb", text(time.monthstr[rec.month], 0, 3)); fmt = fmt.replaceAll("mB", time.monthstr[rec.month]); fmt = fmt.replaceAll("vB", time.weekdays[rec.weekday]); - fmt = fmt.replaceAll("vb", time.weekdays[rec.weekday].slice(0, 3)); + fmt = fmt.replaceAll("vb", text(time.weekdays[rec.weekday], 0, 3)); return fmt; } diff --git a/toml.cm b/toml.cm index 87c7377d..afe6e805 100644 --- a/toml.cm +++ b/toml.cm @@ -1,58 +1,88 @@ // Simple TOML parser for cell modules // Supports basic TOML features needed for the module system +// +// Avoids regex .replace(/.../g, ...) so no global-regex state issues. + +function toml_unescape(s) { + if (!is_text(s)) return null + // Order matters: + // "\\\"" (backslash + quote) should become "\"", not just '"' + // So: unescape \" first, then unescape \\. + s = replace(s, '\\"', '"') + s = replace(s, '\\\\', '\\') + return s +} + +function toml_escape(s) { + if (!is_text(s)) return null + // Order matters: + // escape backslashes first, otherwise escaping quotes introduces new backslashes that would get double-escaped. + s = replace(s, '\\', '\\\\') + s = replace(s, '"', '\\"') + return s +} + +function parse_toml(toml_text) { + if (!is_text(toml_text)) return null + + // Prefer Misty split if present; fall back to JS split. + var lines = array(toml_text, '\n') + if (lines == null) lines = toml_text.split('\n') -function parse_toml(text) { - if (!is_text(text)) return null - var lines = text.split('\n') var result = {} var current_section = result var current_section_name = '' - + for (var i = 0; i < lines.length; i++) { - var line = lines[i].trim() - + var line = trim(lines[i]) + if (line == null) line = lines[i] // Skip empty lines and comments if (!line || line.startsWith('#')) continue - + // Section header if (line.startsWith('[') && line.endsWith(']')) { - var section_path = parse_key_path(line.slice(1, -1)) + var section_path = parse_key_path(text(line, 1, -1)) + if (section_path == null) return null + current_section = result - // Reconstruct name for debugging/legacy (not strictly needed for object construction) - current_section_name = section_path.join('.') - + current_section_name = text(section_path, '.') + for (var j = 0; j < section_path.length; j++) { var key = section_path[j] - if (!current_section[key]) { + + // Only treat null as "missing"; do not clobber false/0/"" + if (current_section[key] == null) { current_section[key] = {} + } else if (!is_object(current_section[key])) { + // Scalar/table collision like: a = 1 then [a.b] + return null } + current_section = current_section[key] } continue } - + // Key-value pair var eq_index = line.indexOf('=') if (eq_index > 0) { - var key_part = line.substring(0, eq_index).trim() - var value = line.substring(eq_index + 1).trim() - - // Handle quoted keys in key-value pairs too if needed? - // For now assuming simple keys or quoted keys + var key_part = trim(line.substring(0, eq_index)) + var value = trim(line.substring(eq_index + 1)) + if (key_part == null) key_part = line.substring(0, eq_index).trim() + if (value == null) value = line.substring(eq_index + 1).trim() + var key = parse_key(key_part) - - // Parse value + if (key == null) return null + if (value.startsWith('"') && value.endsWith('"')) { - // String - unescape quotes - current_section[key] = value.slice(1, -1).replace(/\\"/g, '"') + current_section[key] = toml_unescape(text(value, 1, -1)) + if (current_section[key] == null) return null } else if (value.startsWith('[') && value.endsWith(']')) { - // Array current_section[key] = parse_array(value) + if (current_section[key] == null) return null } else if (value == 'true' || value == 'false') { - // Boolean current_section[key] = value == 'true' } else if (is_number(value)) { - // Number current_section[key] = Number(value) } else { // Unquoted string @@ -60,157 +90,160 @@ function parse_toml(text) { } } } - + return result } function parse_key(str) { - if (str.startsWith('"') && str.endsWith('"')) { - return str.slice(1, -1).replace(/\\"/g, '"') - } - return str + if (!is_text(str)) return null + + if (str.startsWith('"') && str.endsWith('"')) { + var inner = text(str, 1, -1) + return toml_unescape(inner) + } + return str } // Split a key path by dots, respecting quotes function parse_key_path(str) { + if (!is_text(str)) return null + var parts = [] var current = '' var in_quote = false - + for (var i = 0; i < str.length; i++) { var c = str[i] - if (c == '"' && (i==0 || str[i-1] != '\\')) { + if (c == '"' && (i == 0 || str[i - 1] != '\\')) { in_quote = !in_quote - // We don't verify if it's strictly correct TOML quote usage, just rudimentary } else if (c == '.' && !in_quote) { - parts.push(parse_key(current.trim())) + var piece = trim(current) + if (piece == null) piece = current.trim() + parts.push(parse_key(piece)) current = '' continue } current += c } - if (current.trim().length > 0) - parts.push(parse_key(current.trim())) - + + var tail = trim(current) + if (tail == null) tail = current.trim() + if (tail.length > 0) parts.push(parse_key(tail)) + return parts } function parse_array(str) { - // Remove brackets - str = str.slice(1, -1).trim() + if (!is_text(str)) return null + + // Remove brackets and trim + str = text(str, 1, -1) + str = trim(str) + if (str == null) str = text(str, 1, -1).trim() if (!str) return [] - + var items = [] var current = '' var in_quotes = false - + for (var i = 0; i < str.length; i++) { - var char = str[i] - - if (char == '"' && (i == 0 || str[i-1] != '\\')) { + var ch = str[i] + + if (ch == '"' && (i == 0 || str[i - 1] != '\\')) { in_quotes = !in_quotes - current += char - } else if (char == ',' && !in_quotes) { - items.push(parse_value(current.trim())) + current += ch + } else if (ch == ',' && !in_quotes) { + var piece = trim(current) + if (piece == null) piece = current.trim() + items.push(parse_value(piece)) current = '' } else { - current += char + current += ch } } - - if (current.trim()) { - items.push(parse_value(current.trim())) - } - + + var last = trim(current) + if (last == null) last = current.trim() + if (last) items.push(parse_value(last)) + return items } function parse_value(str) { + if (!is_text(str)) return null + if (str.startsWith('"') && str.endsWith('"')) { - return str.slice(1, -1).replace(/\\"/g, '"') - } else if (str == 'true' || str == 'false') { - return str == 'true' - } else if (!isNaN(Number(str))) { - return Number(str) - } else { - return str + return toml_unescape(text(str, 1, -1)) } + if (str == 'true' || str == 'false') return str == 'true' + + // Use your existing numeric test; TOML numeric formats are richer, but this keeps your "module TOML" scope. + if (!isNaN(Number(str))) return Number(str) + + return str } function encode_toml(obj) { var result = [] - + function encode_value(value) { - if (is_text(value)) { - return '"' + value.replace(/"/g, '\\"') + '"' - } else if (is_logical(value)) { - return value ? 'true' : 'false' - } else if (is_number(value)) { - return text(value) - } else if (is_array(value)) { + if (is_text(value)) return '"' + toml_escape(value) + '"' + if (is_logical(value)) return value ? 'true' : 'false' + if (is_number(value)) return text(value) + if (is_array(value)) { var items = [] - for (var i = 0; i < value.length; i++) { - items.push(encode_value(value[i])) - } - return '[' + items.join(', ') + ']' + for (var i = 0; i < value.length; i++) items.push(encode_value(value[i])) + return '[' + text(items, ', ') + ']' } return text(value) } - + function quote_key(k) { - if (k.includes('.') || k.includes('"') || k.includes(' ')) { - return '"' + k.replace(/"/g, '\\"') + '"' - } - return k + if (k.includes('.') || k.includes('"') || k.includes(' ')) { + return '"' + toml_escape(k) + '"' + } + return k } - + // First pass: encode top-level simple values var keys = array(obj) for (var i = 0; i < keys.length; i++) { var key = keys[i] var value = obj[key] - if (!is_object(value)) { - result.push(quote_key(key) + ' = ' + encode_value(value)) - } + if (!is_object(value)) result.push(quote_key(key) + ' = ' + encode_value(value)) } - + // Second pass: encode nested objects - function encode_section(obj, path) { - var keys = array(obj) - + function encode_section(o, path) { + var keys = array(o) for (var i = 0; i < keys.length; i++) { var key = keys[i] - var value = obj[key] - + var value = o[key] + if (is_object(value)) { - // Nested object - create section - // We MUST quote the key segment if it has dots, otherwise it becomes a nested table path var quoted = quote_key(key) var section_path = path ? path + '.' + quoted : quoted - result.push('[' + section_path + ']') - - // First encode direct properties of this section + + // Direct properties var section_keys = array(value) for (var j = 0; j < section_keys.length; j++) { var sk = section_keys[j] var sv = value[sk] - if (!is_object(sv)) { - result.push(quote_key(sk) + ' = ' + encode_value(sv)) - } + if (!is_object(sv)) result.push(quote_key(sk) + ' = ' + encode_value(sv)) } - - // Then encode nested sections + + // Nested sections encode_section(value, section_path) } } } - + encode_section(obj, '') - return result.join('\n') + return text(result, '\n') } return { decode: parse_toml, encode: encode_toml -} \ No newline at end of file +}