diff --git a/add.ce b/add.ce index e8ad68e6..013dc4fd 100644 --- a/add.ce +++ b/add.ce @@ -25,7 +25,7 @@ for (var i = 0; i < args.length; i++) { log.console(" cell add gitea.pockle.world/john/cell-image image") log.console(" cell add ../local-package") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { if (!locator) { locator = args[i] } else if (!alias) { @@ -40,7 +40,7 @@ if (!locator) { } // Resolve relative paths to absolute paths -if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) { +if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { var resolved = fd.realpath(locator) if (resolved) { locator = resolved @@ -50,11 +50,11 @@ if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || f // Generate default alias from locator if (!alias) { // Use the last component of the locator as alias - var parts = locator.split('/') + var parts = array(locator, '/') alias = parts[parts.length - 1] // Remove any version suffix - if (alias.includes('@')) { - alias = alias.split('@')[0] + if (search(alias, '@') != null) { + alias = array(alias, '@')[0] } } diff --git a/bench.ce b/bench.ce index 6cef42e5..c3abb810 100644 --- a/bench.ce +++ b/bench.ce @@ -115,7 +115,7 @@ function parse_args() { var lock = shop.load_lock() if (lock[name]) { target_pkg = name - } else if (name.startsWith('/') && testlib.is_valid_package(name)) { + } else if (starts_with(name, '/') && testlib.is_valid_package(name)) { target_pkg = name } else { if (testlib.is_valid_package('.')) { @@ -144,7 +144,7 @@ function parse_args() { var bench_path = args[0] // Normalize path - add benches/ prefix if not present - if (!bench_path.startsWith('benches/') && !bench_path.startsWith('/')) { + if (!starts_with(bench_path, 'benches/') && !starts_with(bench_path, '/')) { if (!fd.is_file(bench_path + '.cm') && !fd.is_file(bench_path)) { if (fd.is_file('benches/' + bench_path + '.cm') || fd.is_file('benches/' + bench_path)) { bench_path = 'benches/' + bench_path @@ -179,12 +179,12 @@ function collect_benches(package_name, specific_bench) { var bench_files = [] for (var i = 0; i < files.length; i++) { var f = files[i] - if (f.startsWith("benches/") && f.endsWith(".cm")) { + if (starts_with(f, "benches/") && ends_with(f, ".cm")) { if (specific_bench) { - var bench_name = f.substring(0, f.length - 3) + var bench_name = text(f, 0, -3) var match_name = specific_bench - if (!match_name.startsWith('benches/')) match_name = 'benches/' + match_name - var match_base = match_name.endsWith('.cm') ? match_name.substring(0, match_name.length - 3) : match_name + if (!starts_with(match_name, 'benches/')) match_name = 'benches/' + match_name + var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name if (bench_name != match_base) continue } bench_files.push(f) @@ -427,7 +427,7 @@ function run_benchmarks(package_name, specific_bench) { for (var i = 0; i < bench_files.length; i++) { var f = bench_files[i] - var mod_path = f.substring(0, f.length - 3) + var mod_path = text(f, 0, -3) var file_result = { name: f, diff --git a/benchmarks/js_perf.ce b/benchmarks/js_perf.ce index aa12c60a..23e7cdc8 100644 --- a/benchmarks/js_perf.ce +++ b/benchmarks/js_perf.ce @@ -211,7 +211,7 @@ function benchStringOps() { var splitTime = measureTime(function() { var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p"; for (var i = 0; i < iterations.medium; i++) { - var parts = str.split(","); + var parts = array(str, ","); } }); diff --git a/benchmarks/nota.ce b/benchmarks/nota.ce index 39fb42b7..5779b0e9 100644 --- a/benchmarks/nota.ce +++ b/benchmarks/nota.ce @@ -43,7 +43,7 @@ for (var i = 0; i < 100; i++) { // Calculate statistics function getStats(arr) { - def avg = arr.reduce((a, b) => a + b) / arr.length; + def avg = reduce(arr, (a,b) => a+b, 0) / length(arr) def min = min(...arr); def max = max(...arr); return { avg, min, max }; diff --git a/benchmarks/wota_nota_json.ce b/benchmarks/wota_nota_json.ce index c4f750c8..779ade6c 100644 --- a/benchmarks/wota_nota_json.ce +++ b/benchmarks/wota_nota_json.ce @@ -166,18 +166,18 @@ function runBenchmarkForLibrary(lib, bench) { //////////////////////////////////////////////////////////////////////////////// // Find the requested library and scenario -var lib = libraries.find(l => l.name == lib_name); -var bench = benchmarks.find(b => b.name == scenario_name); +var lib = libraries[find(libraries, l => l.name == lib_name)]; +var bench = benchmarks[find(benchmarks, b => b.name == scenario_name)]; if (!lib) { log.console('Unknown library:', lib_name); - log.console('Available libraries:', text(libraries.map(l => l.name), ', ')); + log.console('Available libraries:', text(array(libraries, l => l.name), ', ')); $stop() } if (!bench) { log.console('Unknown scenario:', scenario_name); - log.console('Available scenarios:', text(benchmarks.map(b => b.name), ', ')); + log.console('Available scenarios:', text(array(benchmarks, b => b.name), ', ')); $stop() } diff --git a/build.ce b/build.ce index 5c92889b..efe9cf3e 100644 --- a/build.ce +++ b/build.ce @@ -56,7 +56,7 @@ for (var i = 0; i < args.length; i++) { log.console(' ' + targets[t]) } $stop() - } else if (!args[i].startsWith('-') && !target_package) { + } else if (!starts_with(args[i], '-') && !target_package) { // Positional argument - treat as package locator target_package = args[i] } @@ -64,7 +64,7 @@ for (var i = 0; i < args.length; i++) { // Resolve local paths to absolute paths if (target_package) { - if (target_package == '.' || target_package.startsWith('./') || target_package.startsWith('../') || fd.is_dir(target_package)) { + if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) { var resolved = fd.realpath(target_package) if (resolved) { target_package = resolved diff --git a/build.cm b/build.cm index 5416def4..4b744de9 100644 --- a/build.cm +++ b/build.cm @@ -83,8 +83,8 @@ function get_build_dir() { function ensure_dir(path) { if (fd.stat(path).isDirectory) return - var parts = path.split('/') - var current = path.startsWith('/') ? '/' : '' + var parts = array(path, '/') + var current = starts_with(path, '/') ? '/' : '' for (var i = 0; i < parts.length; i++) { if (parts[i] == '') continue current += parts[i] + '/' @@ -136,8 +136,8 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') { // Add package CFLAGS (resolve relative -I paths) for (var i = 0; i < cflags.length; i++) { var flag = cflags[i] - if (flag.startsWith('-I') && !flag.startsWith('-I/')) { - flag = '-I"' + pkg_dir + '/' + flag.substring(2) + '"' + if (starts_with(flag, '-I') && !starts_with(flag, '-I/')) { + flag = '-I"' + pkg_dir + '/' + text(flag, 2) + '"' } cmd_parts.push(flag) } @@ -149,7 +149,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') { cmd_parts.push('"' + src_path + '"') - var cmd_str = cmd_parts.join(' ') + var cmd_str = text(cmd_parts, ' ') // Content hash: command + file content var file_content = fd.slurp(src_path) @@ -250,8 +250,8 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty var resolved_ldflags = [] for (var i = 0; i < ldflags.length; i++) { var flag = ldflags[i] - if (flag.startsWith('-L') && !flag.startsWith('-L/')) { - flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"' + if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) { + flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"' } resolved_ldflags.push(flag) } @@ -324,7 +324,7 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty cmd_parts.push('-o', '"' + store_path + '"') - var cmd_str = cmd_parts.join(' ') + var cmd_str = text(cmd_parts, ' ') log.console('Linking ' + lib_name + dylib_ext) var ret = os.system(cmd_str) @@ -378,8 +378,8 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out for (var j = 0; j < ldflags.length; j++) { var flag = ldflags[j] // Resolve relative -L paths - if (flag.startsWith('-L') && !flag.startsWith('-L/')) { - flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"' + if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) { + flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"' } all_ldflags.push(flag) } @@ -395,7 +395,7 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out var target_ldflags = toolchains[target].c_link_args || [] var exe_ext = toolchains[target].system == 'windows' ? '.exe' : '' - if (!output.endsWith(exe_ext) && exe_ext) { + if (!ends_with(output, exe_ext) && exe_ext) { output = output + exe_ext } @@ -415,7 +415,7 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out cmd_parts.push('-o', '"' + output + '"') - var cmd_str = cmd_parts.join(' ') + var cmd_str = text(cmd_parts, ' ') log.console('Linking ' + output) var ret = os.system(cmd_str) @@ -439,7 +439,7 @@ Build.build_all_dynamic = function(target, buildtype = 'release') { var results = [] // Build core first - if (packages.indexOf('core') >= 0) { + if (find(packages, 'core') != null) { try { var lib = Build.build_dynamic('core', target, buildtype) results.push({ package: 'core', library: lib }) diff --git a/cellfs.cm b/cellfs.cm index e3ab894c..5cfd20af 100644 --- a/cellfs.cm +++ b/cellfs.cm @@ -24,14 +24,14 @@ function normalize_path(path) { function dirname(path) { var idx = path.lastIndexOf("/") if (idx == -1) return "" - return path.substring(0, idx) + return text(path, 0, idx) } // Helper to get basename from path function basename(path) { var idx = path.lastIndexOf("/") if (idx == -1) return path - return path.substring(idx + 1) + return text(path, idx + 1) } // Helper to join paths @@ -102,17 +102,17 @@ function resolve(path, must_exist) { path = normalize_path(path) // Check for named mount - if (path.startsWith("@")) { + if (starts_with(path, "@")) { var idx = path.indexOf("/") var mount_name = "" var rel_path = "" if (idx == -1) { - mount_name = path.substring(1) + mount_name = text(path, 1) rel_path = "" } else { - mount_name = path.substring(1, idx) - rel_path = path.substring(idx + 1) + mount_name = text(path, 1, idx) + rel_path = text(path, idx + 1) } // Find named mount @@ -229,7 +229,7 @@ function slurpwrite(path, data) { // Check existence function exists(path) { var res = resolve(path, false) - if (path.startsWith("@")) { + if (starts_with(path, "@")) { return mount_exists(res.mount, res.path) } return res != null @@ -365,14 +365,14 @@ function enumerate(path, recurse) { var seen = {} for (var p of all) { - if (p.startsWith(prefix)) { - var rel = p.substring(prefix_len) + if (starts_with(p, prefix)) { + var rel = text(p, prefix_len) if (rel.length == 0) continue - + if (!recurse) { var slash = rel.indexOf('/') if (slash != -1) { - rel = rel.substring(0, slash) + rel = text(rel, 0, slash) } } @@ -394,14 +394,14 @@ function globfs(globs, dir) { function check_neg(path) { for (var g of globs) { - if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true; + if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) return true; } return false; } function check_pos(path) { for (var g of globs) { - if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true; + if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true; } return false; } @@ -442,10 +442,10 @@ function globfs(globs, dir) { var prefix_len = prefix.length for (var p of all) { - if (p.startsWith(prefix)) { - var rel = p.substring(prefix_len) + if (starts_with(p, prefix)) { + var rel = text(p, prefix_len) if (rel.length == 0) continue - + if (!check_neg(rel) && check_pos(rel)) { results.push(rel) } diff --git a/clean.ce b/clean.ce index b7bf3ec3..5b7ca73e 100644 --- a/clean.ce +++ b/clean.ce @@ -53,7 +53,7 @@ for (var i = 0; i < args.length; i++) { log.console(" --deep Apply to full dependency closure") log.console(" --dry-run Show what would be deleted") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { scope = args[i] } } @@ -73,7 +73,7 @@ var is_shop_scope = (scope == 'shop') var is_world_scope = (scope == 'world') if (!is_shop_scope && !is_world_scope) { - if (scope == '.' || scope.startsWith('./') || scope.startsWith('../') || fd.is_dir(scope)) { + if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) { var resolved = fd.realpath(scope) if (resolved) { scope = resolved diff --git a/clone.ce b/clone.ce index 746206e3..e153f9b4 100644 --- a/clone.ce +++ b/clone.ce @@ -18,7 +18,7 @@ var origin = args[0] var target_path = args[1] // Resolve target path to absolute -if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith('../')) { +if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) { var resolved = fd.realpath(target_path) if (resolved) { target_path = resolved @@ -27,12 +27,12 @@ if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith var cwd = fd.realpath('.') if (target_path == '.') { target_path = cwd - } else if (target_path.startsWith('./')) { - target_path = cwd + target_path.substring(1) - } else if (target_path.startsWith('../')) { + } else if (starts_with(target_path, './')) { + target_path = cwd + text(target_path, 1) + } else if (starts_with(target_path, '../')) { // Go up one directory from cwd - var parent = cwd.substring(0, cwd.lastIndexOf('/')) - target_path = parent + target_path.substring(2) + var parent = text(cwd, 0, cwd.lastIndexOf('/')) + target_path = parent + text(target_path, 2) } } } @@ -92,14 +92,14 @@ try { for (var i = 0; i < count; i++) { if (zip.is_directory(i)) continue var filename = zip.get_filename(i) - var parts = filename.split('/') + var parts = array(filename, '/') if (parts.length <= 1) continue // Skip the first directory (repo-commit prefix) parts.shift() var rel_path = text(parts, '/') var full_path = target_path + '/' + rel_path - var dir_path = full_path.substring(0, full_path.lastIndexOf('/')) + var dir_path = text(full_path, 0, full_path.lastIndexOf('/')) // Ensure directory exists if (!fd.is_dir(dir_path)) { diff --git a/config.ce b/config.ce index ca67c73e..45c0da47 100644 --- a/config.ce +++ b/config.ce @@ -31,7 +31,7 @@ function print_help() { // Parse a dot-notation key into path segments function parse_key(key) { - return key.split('.') + return array(key, '.') } // Get a value from nested object using path @@ -161,7 +161,7 @@ switch (command) { 'ar_timer', 'actor_memory', 'net_service', 'reply_timeout', 'actor_max', 'stack_max' ] - if (!valid_system_keys.includes(path[1])) { + if (find(valid_system_keys, path[1]) == null) { log.error("Invalid system key. Valid keys: " + text(valid_system_keys, ', ')) $stop() return diff --git a/fd.cm b/fd.cm index f9309185..f4f4af35 100644 --- a/fd.cm +++ b/fd.cm @@ -16,14 +16,14 @@ fd.globfs = function(globs, dir) { function check_neg(path) { for (var g of globs) { - if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true; + if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) return true; } return false; } function check_pos(path) { for (var g of globs) { - if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true; + if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true; } return false; } diff --git a/fetch.ce b/fetch.ce index 94ab7a7d..7355749e 100644 --- a/fetch.ce +++ b/fetch.ce @@ -24,7 +24,7 @@ for (var i = 0; i < args.length; i++) { log.console("This command ensures that the zip files on disk match what's in") log.console("the lock file. For local packages, this is a no-op.") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { target_pkg = args[i] } } @@ -35,7 +35,7 @@ var packages_to_fetch = [] if (target_pkg) { // Fetch specific package - if (!all_packages.includes(target_pkg)) { + if (find(all_packages, target_pkg) == null) { log.error("Package not found: " + target_pkg) $stop() } diff --git a/graph.ce b/graph.ce index 544f8023..0d291fc6 100644 --- a/graph.ce +++ b/graph.ce @@ -52,7 +52,7 @@ for (var i = 0; i < args.length; i++) { log.console(" --locked Show lock view without links") log.console(" --world Graph all packages in shop") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { target_locator = args[i] } } @@ -81,7 +81,7 @@ function add_node(locator) { effective: get_effective(locator), linked: link_target != null, local: info == 'local', - commit: lock_entry && lock_entry.commit ? lock_entry.commit.substring(0, 8) : null + commit: lock_entry && lock_entry.commit ? text(lock_entry.commit, 0, 8) : null } } @@ -124,7 +124,7 @@ if (show_world) { } // Resolve local paths - if (target_locator == '.' || target_locator.startsWith('./') || target_locator.startsWith('../') || fd.is_dir(target_locator)) { + if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { var resolved = fd.realpath(target_locator) if (resolved) { target_locator = resolved diff --git a/install.ce b/install.ce index e700612d..134c4956 100644 --- a/install.ce +++ b/install.ce @@ -51,7 +51,7 @@ for (var i = 0; i < args.length; i++) { log.console(" --refresh Refresh floating refs before locking") log.console(" --dry-run Show what would be installed") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { locator = args[i] } } @@ -63,7 +63,7 @@ if (!locator) { // Resolve relative paths to absolute paths // Local paths like '.' or '../foo' need to be converted to absolute paths -if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) { +if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { var resolved = fd.realpath(locator) if (resolved) { locator = resolved @@ -87,7 +87,7 @@ function gather_packages(pkg_locator) { visited[pkg_locator] = true // Check if this is a local path that doesn't exist - if (pkg_locator.startsWith('/') && !fd.is_dir(pkg_locator)) { + if (starts_with(pkg_locator, '/') && !fd.is_dir(pkg_locator)) { skipped_packages.push(pkg_locator) log.console(" Skipping missing local package: " + pkg_locator) return diff --git a/internal/engine.cm b/internal/engine.cm index f06053c2..7c44284b 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -37,6 +37,22 @@ globalThis.logical = function(val1) return null; } +globalThis.some = function(arr, pred) { + return find(arr, pred) != null +} + +globalThis.every = function(arr, pred) { + return find(arr, x => not(pred(x))) == null +} + +globalThis.starts_with = function(str, prefix) { + return search(str, prefix) == 0 +} + +globalThis.ends_with = function(str, suffix) { + return search(str, suffix, -length(suffix)) != null +} + var js = use_embed('js') var fd = use_embed('fd') @@ -104,7 +120,7 @@ function caller_data(depth = 0) var file = "nofile" var line = 0 - var caller = Error().stack.split("\n")[1+depth] + var caller = array(Error().stack, "\n")[1+depth] if (caller) { var md = caller.match(/\((.*)\:/) var m = md ? md[1] : "SCRIPT" @@ -778,7 +794,7 @@ $_.clock(_ => { var vals = [] for (var i = 0; i < inject.length; i++) { var key = inject[i] - if (key && key[0] == '$') key = key.substring(1) + if (key && key[0] == '$') key = text(key, 1) if (key == 'fd') vals.push(fd) else vals.push($_[key]) } diff --git a/internal/shop.cm b/internal/shop.cm index 497915d5..27a98058 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -28,8 +28,8 @@ function put_into_cache(content, obj) function ensure_dir(path) { if (fd.stat(path).isDirectory) return - var parts = path.split('/') - var current = path.startsWith('/') ? '/' : '' + var parts = array(path, '/') + var current = starts_with(path, '/') ? '/' : '' for (var i = 0; i < parts.length; i++) { if (parts[i] == '') continue current += parts[i] + '/' @@ -91,7 +91,7 @@ Shop.get_reports_dir = function() { } function get_import_package(name) { - var parts = name.split('/') + var parts = array(name, '/') if (parts.length > 1) return parts[0] @@ -100,17 +100,17 @@ function get_import_package(name) { function is_internal_path(path) { - return path && path.startsWith('internal/') + return path && starts_with(path, 'internal/') } function split_explicit_package_import(path) { if (!path) return null - var parts = path.split('/') + var parts = array(path, '/') if (parts.length < 2) return null - var looks_explicit = path.startsWith('/') || (parts[0] && parts[0].includes('.')) + var looks_explicit = starts_with(path, '/') || (parts[0] && search(parts[0], '.') != null) if (!looks_explicit) return null // Find the longest prefix that is an installed package @@ -158,8 +158,8 @@ function abs_path_to_package(package_dir) } } - if (package_dir.startsWith(packages_prefix)) - return package_dir.substring(packages_prefix.length) + if (starts_with(package_dir, packages_prefix)) + return text(package_dir, packages_prefix.length) // Check if this local path is the target of a link // If so, return the canonical package name (link origin) instead @@ -195,9 +195,9 @@ Shop.file_info = function(file) { name: null } - if (file.endsWith(MOD_EXT)) + if (ends_with(file, MOD_EXT)) info.is_module = true - else if (file.endsWith(ACTOR_EXT)) + else if (ends_with(file, ACTOR_EXT)) info.is_actor = true // Find package directory and determine package name @@ -206,11 +206,11 @@ Shop.file_info = function(file) { info.package = abs_path_to_package(pkg_dir) if (info.is_actor) - info.name = file.substring(pkg_dir.length + 1, file.length - ACTOR_EXT.length) + info.name = text(file, pkg_dir.length + 1, file.length - ACTOR_EXT.length) else if (info.is_module) - info.name = file.substring(pkg_dir.length + 1, file.length - MOD_EXT.length) + info.name = text(file, pkg_dir.length + 1, file.length - MOD_EXT.length) else - info.name = file.substring(pkg_dir.length + 1) + info.name = text(file, pkg_dir.length + 1) } return info @@ -218,7 +218,7 @@ Shop.file_info = function(file) { function get_import_name(path) { - var parts = path.split('/') + var parts = array(path, '/') if (parts.length < 2) return null return text(array(parts, 1), '/') } @@ -246,7 +246,7 @@ function safe_package_path(pkg) { // For absolute paths, replace / with _ to create a valid directory name // Also replace @ with _ - if (pkg && pkg.startsWith('/')) + if (pkg && starts_with(pkg, '/')) return pkg.replaceAll('/', '_').replaceAll('@', '_') return pkg.replaceAll('@', '_') } @@ -290,8 +290,8 @@ Shop.save_lock = function(lock) { // Get information about how to resolve a package // Local packages always start with / Shop.resolve_package_info = function(pkg) { - if (pkg.startsWith('/')) return 'local' - if (pkg.includes('gitea')) return 'gitea' + if (starts_with(pkg, '/')) return 'local' + if (search(pkg, 'gitea') != null) return 'gitea' return null } @@ -301,8 +301,8 @@ Shop.verify_package_name = function(pkg) { if (pkg == 'local') throw Error("local is not a valid package name") if (pkg == 'core') throw Error("core is not a valid package name") - if (pkg.includes('://')) - throw Error(`Invalid package name: ${pkg}; did you mean ${pkg.split('://')[1]}?`) + if (search(pkg, '://') != null) + throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`) } // Convert module package to download URL @@ -310,7 +310,7 @@ Shop.get_download_url = function(pkg, commit_hash) { var info = Shop.resolve_package_info(pkg) if (info == 'gitea') { - var parts = pkg.split('/') + var parts = array(pkg, '/') var host = parts[0] var user = parts[1] var repo = parts[2] @@ -326,7 +326,7 @@ Shop.get_api_url = function(pkg) { var info = Shop.resolve_package_info(pkg) if (info == 'gitea') { - var parts = pkg.split('/') + var parts = array(pkg, '/') var host = parts[0] var user = parts[1] var repo = parts[2] @@ -361,7 +361,7 @@ var open_dls = {} var SHOP_DEFAULT_INJECT = ['$self', '$overling', '$clock', '$delay', '$start', '$receiver', '$contact', '$portal', '$time_limit', '$couple', '$stop', '$unneeded', '$connection', '$fd'] function strip_dollar(name) { - if (name && name[0] == '$') return name.substring(1) + if (name && name[0] == '$') return text(name, 1) return name } @@ -470,7 +470,7 @@ function resolve_locator(path, ctx) // If ctx is an absolute path (starts with /), use it directly // Otherwise, look it up in the packages directory var ctx_dir - if (ctx.startsWith('/')) { + if (starts_with(ctx, '/')) { ctx_dir = ctx } else { ctx_dir = get_packages_dir() + '/' + safe_package_path(ctx) @@ -541,7 +541,7 @@ Shop.open_package_dylib = function(pkg) { var resolved_pkg = link_target ? link_target : pkg var pkg_dir; - if (resolved_pkg.startsWith('/')) { + if (starts_with(resolved_pkg, '/')) { pkg_dir = resolved_pkg } else { pkg_dir = get_packages_dir() + '/' + safe_package_path(resolved_pkg) @@ -701,10 +701,10 @@ var module_info_cache = {} function resolve_module_info(path, package_context) { var lookup_key = package_context ? package_context + ':' + path : ':' + path - + if (module_info_cache[lookup_key]) return module_info_cache[lookup_key] - + 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 = min(c_resolve.scope, mod_resolve.scope) @@ -990,7 +990,7 @@ Shop.extract = function(pkg) { if (lock_entry && lock_entry.commit) { var extracted_commit_file = target_dir + '/.cell_commit' if (fd.is_file(extracted_commit_file)) { - var extracted_commit = text(fd.slurp(extracted_commit_file)).trim() + var extracted_commit = trim(text(fd.slurp(extracted_commit_file))) if (extracted_commit == lock_entry.commit) { // Already extracted at this commit, skip return true @@ -1100,13 +1100,13 @@ function install_zip(zip_blob, target_dir) { for (var i = 0; i < count; i++) { if (zip.is_directory(i)) continue var filename = zip.get_filename(i) - var parts = filename.split('/') + var parts = array(filename, '/') if (parts.length <= 1) continue parts.shift() var rel_path = text(parts, '/') var full_path = target_dir + '/' + rel_path - var dir_path = full_path.substring(0, full_path.lastIndexOf('/')) + var dir_path = text(full_path, 0, full_path.lastIndexOf('/')) if (!created_dirs[dir_path]) { ensure_dir(dir_path) @@ -1203,7 +1203,7 @@ function get_package_scripts(package) for (var i = 0; i < files.length; i++) { var file = files[i] - if (file.endsWith('.cm') || file.endsWith('.ce')) { + if (ends_with(file, '.cm') || ends_with(file, '.ce')) { scripts.push(file) } } @@ -1251,7 +1251,7 @@ Shop.get_package_dir = function(pkg) { // -> 'js_gitea_pockle_world_john_prosperon_sprite_use' Shop.c_symbol_for_file = function(pkg, file) { var pkg_safe = pkg.replaceAll(/\//g, '_').replaceAll(/\./g, '_').replaceAll(/-/g, '_') - var file_safe = file.substring(0, file.lastIndexOf('.')).replaceAll(/\//g, '_').replaceAll(/\./g, '_').replaceAll(/-/g, '_') + var file_safe = text(file, 0, file.lastIndexOf('.')).replaceAll(/\//g, '_').replaceAll(/\./g, '_').replaceAll(/-/g, '_') return 'js_' + pkg_safe + '_' + file_safe + '_use' } @@ -1291,15 +1291,15 @@ Shop.parse_package = function(locator) { // Strip version suffix if present var clean = locator - if (locator.includes('@')) { - clean = locator.split('@')[0] + if (search(locator, '@') != null) { + clean = array(locator, '@')[0] } var info = Shop.resolve_package_info(clean) if (!info) return null // Extract package name (last component of path) - var parts = clean.split('/') + var parts = array(clean, '/') var name = parts[parts.length - 1] return { diff --git a/internal/testlib.cm b/internal/testlib.cm index b50e1d22..6d6a37ea 100644 --- a/internal/testlib.cm +++ b/internal/testlib.cm @@ -24,7 +24,7 @@ function get_pkg_dir(package_name) { if (!package_name) { return fd.realpath('.') } - if (package_name.startsWith('/')) { + if (starts_with(package_name, '/')) { return package_name } var shop = use('internal/shop') @@ -35,8 +35,8 @@ function get_pkg_dir(package_name) { function ensure_dir(path) { if (fd.is_dir(path)) return true - var parts = path.split('/') - var current = path.startsWith('/') ? '/' : '' + var parts = array(path, '/') + var current = starts_with(path, '/') ? '/' : '' for (var i = 0; i < parts.length; i++) { if (parts[i] == '') continue current += parts[i] + '/' diff --git a/link.ce b/link.ce index 4820a732..0b080962 100644 --- a/link.ce +++ b/link.ce @@ -108,11 +108,11 @@ if (cmd == 'list') { // Resolve target if it's a local path if (target == '.' || fd.is_dir(target)) { target = fd.realpath(target) - } else if (target.startsWith('./') || target.startsWith('../')) { + } else if (starts_with(target, './') || starts_with(target, '../')) { // Relative path that doesn't exist yet - try to resolve anyway var cwd = fd.realpath('.') - if (target.startsWith('./')) { - target = cwd + target.substring(1) + if (starts_with(target, './')) { + target = cwd + text(target, 1) } else { // For ../ paths, var fd.realpath handle it if possible target = fd.realpath(target) || target @@ -127,7 +127,7 @@ if (cmd == 'list') { // Resolve path if (target == '.' || fd.is_dir(target)) { target = fd.realpath(target) - } else if (target.startsWith('./') || target.startsWith('../')) { + } else if (starts_with(target, './') || starts_with(target, '../')) { target = fd.realpath(target) || target } @@ -158,7 +158,7 @@ if (cmd == 'list') { } // Validate: if target is a local path, it must have cell.toml - if (target.startsWith('/')) { + if (starts_with(target, '/')) { if (!fd.is_file(target + '/cell.toml')) { log.console("Error: " + target + " is not a valid package (no cell.toml)") $stop() diff --git a/link.cm b/link.cm index 88e92c98..c025e637 100644 --- a/link.cm +++ b/link.cm @@ -21,7 +21,7 @@ function get_packages_dir() { // return the safe path for the package function safe_package_path(pkg) { // For absolute paths, replace / with _ to create a valid directory name - if (pkg && pkg.startsWith('/')) + if (pkg && starts_with(pkg, '/')) return pkg.replaceAll('/', '_').replaceAll('@', '_') return pkg.replaceAll('@', '_') } @@ -32,8 +32,8 @@ function get_package_abs_dir(package) { function ensure_dir(path) { if (fd.stat(path).isDirectory) return - var parts = path.split('/') - var current = path.startsWith('/') ? '/' : '' + var parts = array(path, '/') + var current = starts_with(path, '/') ? '/' : '' for (var i = 0; i < parts.length; i++) { if (parts[i] == '') continue current += parts[i] + '/' @@ -47,7 +47,7 @@ function ensure_dir(path) { // If target is a local path (starts with /), return it directly // If target is a package name, return the package directory function resolve_link_target(target) { - if (target.startsWith('/')) { + if (starts_with(target, '/')) { return target } // Target is another package - resolve to its directory @@ -94,7 +94,7 @@ Link.add = function(canonical, target, shop) { } // Validate target is a valid package - if (target.startsWith('/')) { + if (starts_with(target, '/')) { // Local path - must have cell.toml if (!fd.is_file(target + '/cell.toml')) { throw Error('Target ' + target + ' is not a valid package (no cell.toml)') @@ -113,7 +113,7 @@ Link.add = function(canonical, target, shop) { // Install dependencies of the linked package // Read the target's cell.toml to find its dependencies - var target_path = target.startsWith('/') ? target : get_package_abs_dir(target) + var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target) var toml_path = target_path + '/cell.toml' if (fd.is_file(toml_path)) { try { @@ -123,7 +123,7 @@ Link.add = function(canonical, target, shop) { for (var alias in cfg.dependencies) { var dep_locator = cfg.dependencies[alias] // Skip local dependencies that don't exist - if (dep_locator.startsWith('/') && !fd.is_dir(dep_locator)) { + if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) { log.console(" Skipping missing local dependency: " + dep_locator) continue } @@ -184,7 +184,7 @@ Link.sync_one = function(canonical, target, shop) { var link_target = resolve_link_target(target) // Ensure parent directories exist - var parent = target_dir.substring(0, target_dir.lastIndexOf('/')) + var parent = text(target_dir, 0, target_dir.lastIndexOf('/')) ensure_dir(parent) // Check current state @@ -241,7 +241,7 @@ Link.sync_all = function(shop) { for (var alias in cfg.dependencies) { var dep_locator = cfg.dependencies[alias] // Skip local dependencies that don't exist - if (dep_locator.startsWith('/') && !fd.is_dir(dep_locator)) { + if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) { continue } // Install the dependency if not already in shop diff --git a/list.ce b/list.ce index ce8499d2..2cc50d5f 100644 --- a/list.ce +++ b/list.ce @@ -31,7 +31,7 @@ if (args && args.length > 0) { target_pkg = args[0] // Resolve local paths - if (target_pkg == '.' || target_pkg.startsWith('./') || target_pkg.startsWith('../') || fd.is_dir(target_pkg)) { + if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { var resolved = fd.realpath(target_pkg) if (resolved) { target_pkg = resolved @@ -60,7 +60,7 @@ function print_deps(ctx, indent) { var aliases = [] for (var k in deps) aliases.push(k) - aliases.sort() + aliases = sort(aliases) if (aliases.length == 0) { log.console(indent + " (none)") @@ -84,7 +84,7 @@ function print_deps(ctx, indent) { status.push("linked -> " + link_target) } if (lock_entry && lock_entry.commit) { - status.push("@" + lock_entry.commit.substring(0, 8)) + status.push("@" + text(lock_entry.commit, 0, 8)) } if (lock_entry && lock_entry.type == 'local') { status.push("local") @@ -115,7 +115,7 @@ if (mode == 'local') { if (packages.length == 0) { log.console(" (none)") } else { - packages.sort() + packages = sort(packages) // Group by type var local_pkgs = [] @@ -157,7 +157,7 @@ if (mode == 'local') { log.console("Remote packages:") for (var p of remote_pkgs) { var lock_entry = lock[p] - var commit = lock_entry && lock_entry.commit ? " @" + lock_entry.commit.substring(0, 8) : "" + var commit = lock_entry && lock_entry.commit ? " @" + text(lock_entry.commit, 0, 8) : "" log.console(" " + p + commit) } log.console("") diff --git a/ls.ce b/ls.ce index c8731b56..ba3e1bcb 100644 --- a/ls.ce +++ b/ls.ce @@ -11,7 +11,7 @@ var modules = package.list_modules(pkg) var programs = package.list_programs(pkg) log.console("Modules in " + pkg + ":") -modules.sort() +modules = sort(modules) if (modules.length == 0) { log.console(" (none)") } else { @@ -22,7 +22,7 @@ if (modules.length == 0) { log.console("") log.console("Programs in " + pkg + ":") -programs.sort() +programs = sort(programs) if (programs.length == 0) { log.console(" (none)") } else { diff --git a/pack.ce b/pack.ce index 9af2c260..f5740414 100644 --- a/pack.ce +++ b/pack.ce @@ -79,7 +79,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/package.cm b/package.cm index e8b4901e..1072f2c2 100644 --- a/package.cm +++ b/package.cm @@ -11,7 +11,7 @@ var link = use('link') // For remote packages, keep slashes as they use nested directories function safe_package_path(pkg) { if (!pkg) return pkg - if (pkg.startsWith('/')) + if (starts_with(pkg, '/')) return pkg.replaceAll('/', '_').replaceAll('@', '_') return pkg.replaceAll('@', '_') } @@ -22,7 +22,7 @@ function get_path(name) if (!name) return fd.realpath('.') // If name is already an absolute path, use it directly - if (name.startsWith('/')) + if (starts_with(name, '/')) return name // Check if this package is linked - if so, use the link target directly @@ -30,7 +30,7 @@ function get_path(name) var link_target = link.get_target(name) if (link_target) { // If link target is a local path, use it directly - if (link_target.startsWith('/')) + if (starts_with(link_target, '/')) return link_target // Otherwise it's another package name, resolve that return os.global_shop_path + '/packages/' + link_target.replaceAll('@', '_') @@ -44,11 +44,12 @@ package.load_config = function(name) { var config_path = get_path(name) + '/cell.toml' - if (!fd.is_file(config_path)) + if (!fd.is_file(config_path)) { throw Error(`${config_path} does not exist`) + } var content = text(fd.slurp(config_path)) - if (!content || content.trim().length == 0) + if (!content || length(trim(content)) == 0) return {} var result = toml.decode(content) @@ -121,7 +122,7 @@ package.find_package_dir = function(file) var dir = absolute if (fd.is_file(dir)) { var last_slash = dir.lastIndexOf('/') - if (last_slash > 0) dir = dir.substring(0, last_slash) + if (last_slash > 0) dir = text(dir, 0, last_slash) } while (dir && dir.length > 0) { @@ -131,7 +132,7 @@ package.find_package_dir = function(file) } var last_slash = dir.lastIndexOf('/') if (last_slash <= 0) break - dir = dir.substring(0, last_slash) + dir = text(dir, 0, last_slash) } return null @@ -148,7 +149,7 @@ package.split_alias = function(name, path) return null } - var parts = path.split('/') + var parts = array(path, '/') var first_part = parts[0] try { @@ -205,7 +206,7 @@ package.list_files = function(pkg) { for (var i = 0; i < list.length; i++) { var item = list[i] if (item == '.' || item == '..') continue - if (item.startsWith('.')) continue + if (starts_with(item, '.')) continue // Skip build directories in root @@ -231,8 +232,8 @@ package.list_modules = function(name) { var files = package.list_files(name) var modules = [] for (var i = 0; i < files.length; i++) { - if (files[i].endsWith('.cm')) { - modules.push(files[i].substring(0, files[i].length - 3)) + if (ends_with(files[i], '.cm')) { + modules.push(text(files[i], 0, -3)) } } return modules @@ -242,8 +243,8 @@ package.list_programs = function(name) { var files = package.list_files(name) var programs = [] for (var i = 0; i < files.length; i++) { - if (files[i].endsWith('.ce')) { - programs.push(files[i].substring(0, files[i].length - 3)) + if (ends_with(files[i], '.ce')) { + programs.push(text(files[i], 0, -3)) } } return programs @@ -260,13 +261,13 @@ package.get_flags = function(name, flag_type, target) { // Base flags if (config.compilation && config.compilation[flag_type]) { var base = config.compilation[flag_type] - flags = flags.concat(base.split(/\s+/).filter(function(f) { return f.length > 0 })) + flags = flags.concat(filter(array(base, /\s+/), function(f) { return f.length > 0 })) } // Target-specific flags if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) { var target_flags = config.compilation[target][flag_type] - flags = flags.concat(target_flags.split(/\s+/).filter(function(f) { return f.length > 0 })) + flags = flags.concat(filter(array(target_flags, /\s+/), function(f) { return f.length > 0 })) } return flags @@ -285,16 +286,16 @@ package.get_c_files = function(name, target, exclude_main) { for (var i = 0; i < files.length; i++) { var file = files[i] - if (!file.endsWith('.c') && !file.endsWith('.cpp')) continue + if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue - var ext = file.endsWith('.cpp') ? '.cpp' : '.c' - var base = file.substring(0, file.length - ext.length) + var ext = ends_with(file, '.cpp') ? '.cpp' : '.c' + var base = text(file, 0, -ext.length) var dir = '' var name_part = base var slash = base.lastIndexOf('/') if (slash >= 0) { - dir = base.substring(0, slash + 1) - name_part = base.substring(slash + 1) + dir = text(base, 0, slash + 1) + name_part = text(base, slash + 1) } // Check for target suffix @@ -304,10 +305,10 @@ package.get_c_files = function(name, target, exclude_main) { for (var t = 0; t < known_targets.length; t++) { var suffix = '_' + known_targets[t] - if (name_part.endsWith(suffix)) { + if (ends_with(name_part, suffix)) { is_variant = true variant_target = known_targets[t] - generic_name = name_part.substring(0, name_part.length - suffix.length) + generic_name = text(name_part, 0, -suffix.length) break } } @@ -342,8 +343,8 @@ package.get_c_files = function(name, target, exclude_main) { if (exclude_main) { var basename = selected var s = selected.lastIndexOf('/') - if (s >= 0) basename = selected.substring(s + 1) - if (basename == 'main.c' || basename.startsWith('main_')) continue + if (s >= 0) basename = text(selected, s + 1) + if (basename == 'main.c' || starts_with(basename, 'main_')) continue } result.push(selected) } diff --git a/pronto.cm b/pronto.cm index 0a3a8694..9d491cc1 100644 --- a/pronto.cm +++ b/pronto.cm @@ -14,7 +14,7 @@ function is_requestor(fn) { } function check_requestors(list, factory) { - if (!is_array(list) || list.some(r => !is_requestor(r))) + if (!is_array(list) || some(list, r => !is_requestor(r))) throw make_reason(factory, 'Bad requestor array.', list) } @@ -106,7 +106,7 @@ function parallel(requestor_array, throttle, need) { function cancel(reason) { if (finished) return finished = true - cancel_list.forEach(c => { + arrfor(cancel_list, c => { try { if (is_function(c)) c(reason) } catch (_) {} }) } @@ -153,6 +153,7 @@ function parallel(requestor_array, throttle, need) { } } + def concurrent = throttle ? min(throttle, length) : length for (var i = 0; i < concurrent; i++) start_one() @@ -188,7 +189,7 @@ function race(requestor_array, throttle, need) { function cancel(reason) { if (finished) return finished = true - cancel_list.forEach(c => { + arrfor(cancel_list, c => { try { if (is_function(c)) c(reason) } catch (_) {} }) } @@ -330,7 +331,7 @@ function objectify(factory_fn) { throw make_reason(factory, 'Expected an object.', object_of_requestors) def keys = array(object_of_requestors) - def requestor_array = keys.map(k => object_of_requestors[k]) + def requestor_array = array(keys, k => object_of_requestors[k]) def inner_requestor = factory_fn(requestor_array, ...rest) @@ -340,7 +341,7 @@ function objectify(factory_fn) { callback(null, reason) } else if (is_array(results)) { def obj = {} - keys.forEach((k, i) => { obj[k] = results[i] }) + arrfor(keys, (k, i) => { obj[k] = results[i] }) callback(obj, reason) } else { callback(results, reason) diff --git a/qopconv.ce b/qopconv.ce index e4c6d6c2..748bfa72 100644 --- a/qopconv.ce +++ b/qopconv.ce @@ -52,10 +52,10 @@ function unpack(archive_path) { var data = archive.read(f) if (data) { // Ensure directory exists - var dir = f.substring(0, f.lastIndexOf('/')) + var dir = text(f, 0, f.lastIndexOf('/')) if (dir) { // recursive mkdir - var parts = dir.split('/') + var parts = array(dir, '/') var curr = "." for (var p of parts) { curr += "/" + p diff --git a/remove.ce b/remove.ce index cb951aef..f5f4db4f 100644 --- a/remove.ce +++ b/remove.ce @@ -31,7 +31,7 @@ for (var i = 0; i < args.length; i++) { log.console(" --prune Also remove packages no longer needed by any root") log.console(" --dry-run Show what would be removed") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { target_pkg = args[i] } } @@ -42,7 +42,7 @@ if (!target_pkg) { } // Resolve relative paths to absolute paths -if (target_pkg == '.' || target_pkg.startsWith('./') || target_pkg.startsWith('../') || fd.is_dir(target_pkg)) { +if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { var resolved = fd.realpath(target_pkg) if (resolved) { target_pkg = resolved @@ -77,7 +77,7 @@ if (prune) { // Find packages that are NOT needed for (var p of all_packages) { if (p == 'core') continue - if (!needed[p] && packages_to_remove.indexOf(p) < 0) { + if (!needed[p] && find(packages_to_remove, p) == null) { packages_to_remove.push(p) } } diff --git a/resolve.ce b/resolve.ce index 5df828bc..a0029656 100644 --- a/resolve.ce +++ b/resolve.ce @@ -43,7 +43,7 @@ for (var i = 0; i < args.length; i++) { log.console(" --locked Show lock state without applying links") log.console(" --refresh Refresh floating refs before printing") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { target_locator = args[i] } } @@ -54,7 +54,7 @@ if (!target_locator) { } // Resolve local paths -if (target_locator == '.' || target_locator.startsWith('./') || target_locator.startsWith('../') || fd.is_dir(target_locator)) { +if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { var resolved = fd.realpath(target_locator) if (resolved) { target_locator = resolved @@ -114,10 +114,9 @@ var sorted = [] for (var locator in all_deps) { sorted.push({ locator: locator, depth: all_deps[locator].depth }) } -sorted.sort(function(a, b) { - if (a.depth != b.depth) return a.depth - b.depth - return a.locator < b.locator ? -1 : 1 -}) + +sorted = sort(sorted, "locator") +sorted = sort(sorted, "depth") for (var i = 0; i < sorted.length; i++) { var locator = sorted[i].locator @@ -158,7 +157,7 @@ for (var i = 0; i < sorted.length; i++) { var commit_str = "" if (lock_entry && lock_entry.commit) { - commit_str = " @" + lock_entry.commit.substring(0, 8) + commit_str = " @" + text(lock_entry.commit, 0, 8) } else if (lock_entry && lock_entry.type == 'local') { commit_str = " (local)" } diff --git a/search.ce b/search.ce index 1ea23786..ad6049b1 100644 --- a/search.ce +++ b/search.ce @@ -11,7 +11,7 @@ if (args.length < 1) { return } -var query = args[0].toLowerCase() +var query = args[0]) var found_packages = [] var found_modules = [] var found_actors = [] @@ -21,7 +21,7 @@ var packages = shop.list_packages() for (var package_name of packages) { // Check if package name matches - if (package_name.toLowerCase().includes(query)) { + if (search(package_name), query) != null) { found_packages.push(package_name) } @@ -29,14 +29,14 @@ for (var package_name of packages) { try { var modules = pkg.list_modules(package_name) for (var mod of modules) { - if (mod.toLowerCase().includes(query)) { + if (search(mod), query) != null) { found_modules.push(package_name + ':' + mod) } } var actors = pkg.list_programs(package_name) for (var actor of actors) { - if (actor.toLowerCase().includes(query)) { + if (search(actor), query) != null) { found_actors.push(package_name + ':' + actor) } } diff --git a/source/quickjs.c b/source/quickjs.c index a6101539..88d28492 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -1901,7 +1901,6 @@ JSContext *JS_NewContext(JSRuntime *rt) JS_AddIntrinsicBaseObjects(ctx); JS_AddIntrinsicEval(ctx); - JS_AddIntrinsicStringNormalize(ctx); JS_AddIntrinsicRegExp(ctx); return ctx; @@ -29593,106 +29592,6 @@ static int JS_isConcatSpreadable(JSContext *ctx, JSValueConst obj) return JS_IsArray(ctx, obj); } -static JSValue js_array_at(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj, ret; - int64_t len, idx; - JSValue *arrp; - uint32_t count; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - if (JS_ToInt64Sat(ctx, &idx, argv[0])) - goto exception; - - if (idx < 0) - idx = len + idx; - if (idx < 0 || idx >= len) { - ret = JS_NULL; - } else if (js_get_fast_array(ctx, obj, &arrp, &count) && idx < count) { - ret = JS_DupValue(ctx, arrp[idx]); - } else { - int present = JS_TryGetPropertyInt64(ctx, obj, idx, &ret); - if (present < 0) - goto exception; - if (!present) - ret = JS_NULL; - } - JS_FreeValue(ctx, obj); - return ret; - exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_array_with(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue arr, obj, ret, *arrp, *pval; - JSObject *p; - int64_t i, len, idx; - uint32_t count32; - - ret = JS_EXCEPTION; - arr = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - if (JS_ToInt64Sat(ctx, &idx, argv[0])) - goto exception; - - if (idx < 0) - idx = len + idx; - - if (idx < 0 || idx >= len) { - JS_ThrowRangeError(ctx, "invalid array index: %" PRId64, idx); - goto exception; - } - - arr = js_allocate_fast_array(ctx, len); - if (JS_IsException(arr)) - goto exception; - - p = JS_VALUE_GET_OBJ(arr); - i = 0; - pval = p->u.array.u.values; - if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { - for (; i < idx; i++, pval++) - *pval = JS_DupValue(ctx, arrp[i]); - *pval = JS_DupValue(ctx, argv[1]); - for (i++, pval++; i < len; i++, pval++) - *pval = JS_DupValue(ctx, arrp[i]); - } else { - for (; i < idx; i++, pval++) - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) - goto fill_and_fail; - *pval = JS_DupValue(ctx, argv[1]); - for (i++, pval++; i < len; i++, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - fill_and_fail: - for (; i < len; i++, pval++) - *pval = JS_NULL; - goto exception; - } - } - } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; - - ret = arr; - arr = JS_NULL; - -exception: - JS_FreeValue(ctx, arr); - JS_FreeValue(ctx, obj); - return ret; -} - static JSValue js_array_concat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -29759,225 +29658,6 @@ exception: return JS_EXCEPTION; } -#define special_every 0 -#define special_some 1 -#define special_forEach 2 -#define special_map 3 -#define special_filter 4 - -static JSValue js_array_every(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int special) -{ - JSValue obj, val, index_val, res, ret; - JSValueConst args[3]; - JSValueConst func, this_arg; - int64_t len, k, n; - int present; - - ret = JS_NULL; - val = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - func = argv[0]; - this_arg = JS_NULL; - if (argc > 1) - this_arg = argv[1]; - - if (check_function(ctx, func)) - goto exception; - - switch (special) { - case special_every: - ret = JS_TRUE; - break; - case special_some: - ret = JS_FALSE; - break; - case special_map: - /* XXX: JS_ArraySpeciesCreate should take int64_t */ - ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64(ctx, len)); - if (JS_IsException(ret)) - goto exception; - break; - case special_filter: - ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); - if (JS_IsException(ret)) - goto exception; - break; - } - n = 0; - - for(k = 0; k < len; k++) { - present = JS_TryGetPropertyInt64(ctx, obj, k, &val); - if (present < 0) - goto exception; - if (present) { - index_val = JS_NewInt64(ctx, k); - if (JS_IsException(index_val)) - goto exception; - args[0] = val; - args[1] = index_val; - args[2] = obj; - res = JS_Call(ctx, func, this_arg, 3, args); - JS_FreeValue(ctx, index_val); - if (JS_IsException(res)) - goto exception; - switch (special) { - case special_every: - if (!JS_ToBoolFree(ctx, res)) { - ret = JS_FALSE; - goto done; - } - break; - case special_some: - if (JS_ToBoolFree(ctx, res)) { - ret = JS_TRUE; - goto done; - } - break; - case special_map: - if (JS_DefinePropertyValueInt64(ctx, ret, k, res, - JS_PROP_C_W_E | JS_PROP_THROW) < 0) - goto exception; - break; - case special_filter: - if (JS_ToBoolFree(ctx, res)) { - if (JS_DefinePropertyValueInt64(ctx, ret, n++, JS_DupValue(ctx, val), - JS_PROP_C_W_E | JS_PROP_THROW) < 0) - goto exception; - } - break; - default: - JS_FreeValue(ctx, res); - break; - } - JS_FreeValue(ctx, val); - val = JS_NULL; - } - } -done: - JS_FreeValue(ctx, val); - JS_FreeValue(ctx, obj); - return ret; - -exception: - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, val); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -#define special_reduce 0 -#define special_reduceRight 1 - -static JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int special) -{ - JSValue obj, val, index_val, acc, acc1; - JSValueConst args[4]; - JSValueConst func; - int64_t len, k, k1; - int present; - - acc = JS_NULL; - val = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - func = argv[0]; - - if (check_function(ctx, func)) - goto exception; - - k = 0; - if (argc > 1) { - acc = JS_DupValue(ctx, argv[1]); - } else { - for(;;) { - if (k >= len) { - JS_ThrowTypeError(ctx, "empty array"); - goto exception; - } - k1 = (special & special_reduceRight) ? len - k - 1 : k; - k++; - present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc); - if (present < 0) - goto exception; - if (present) - break; - } - } - for (; k < len; k++) { - k1 = (special & special_reduceRight) ? len - k - 1 : k; - present = JS_TryGetPropertyInt64(ctx, obj, k1, &val); - if (present < 0) - goto exception; - - if (present) { - index_val = JS_NewInt64(ctx, k1); - if (JS_IsException(index_val)) - goto exception; - args[0] = acc; - args[1] = val; - args[2] = index_val; - args[3] = obj; - acc1 = JS_Call(ctx, func, JS_NULL, 4, args); - JS_FreeValue(ctx, index_val); - JS_FreeValue(ctx, val); - val = JS_NULL; - if (JS_IsException(acc1)) - goto exception; - JS_FreeValue(ctx, acc); - acc = acc1; - } - } - JS_FreeValue(ctx, obj); - return acc; - -exception: - JS_FreeValue(ctx, acc); - JS_FreeValue(ctx, val); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_array_fill(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj; - int64_t len, start, end; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - start = 0; - if (argc > 1 && !JS_IsNull(argv[1])) { - if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len)) - goto exception; - } - - end = len; - if (argc > 2 && !JS_IsNull(argv[2])) { - if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len)) - goto exception; - } - - /* XXX: should special case fast arrays */ - while (start < end) { - if (JS_SetPropertyInt64(ctx, obj, start, - JS_DupValue(ctx, argv[0])) < 0) - goto exception; - start++; - } - return obj; - - exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -30028,174 +29708,6 @@ static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; } -static JSValue js_array_indexOf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj, val; - int64_t len, n, res; - JSValue *arrp; - uint32_t count; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - res = -1; - if (len > 0) { - n = 0; - if (argc > 1) { - if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len)) - goto exception; - } - if (js_get_fast_array(ctx, obj, &arrp, &count)) { - for (; n < count; n++) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), - JS_DupValue(ctx, arrp[n]), JS_EQ_STRICT)) { - res = n; - goto done; - } - } - } - for (; n < len; n++) { - int present = JS_TryGetPropertyInt64(ctx, obj, n, &val); - if (present < 0) - goto exception; - if (present) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) { - res = n; - break; - } - } - } - } - done: - JS_FreeValue(ctx, obj); - return JS_NewInt64(ctx, res); - - exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj, val; - int64_t len, n, res; - int present; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - res = -1; - if (len > 0) { - n = len - 1; - if (argc > 1) { - if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len)) - goto exception; - } - /* XXX: should special case fast arrays */ - for (; n >= 0; n--) { - present = JS_TryGetPropertyInt64(ctx, obj, n, &val); - if (present < 0) - goto exception; - if (present) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) { - res = n; - break; - } - } - } - } - JS_FreeValue(ctx, obj); - return JS_NewInt64(ctx, res); - - exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -enum { - ArrayFind, - ArrayFindIndex, - ArrayFindLast, - ArrayFindLastIndex, -}; - -static JSValue js_array_find(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int mode) -{ - JSValueConst func, this_arg; - JSValueConst args[3]; - JSValue obj, val, index_val, res; - int64_t len, k, end; - int dir; - - index_val = JS_NULL; - val = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - func = argv[0]; - if (check_function(ctx, func)) - goto exception; - - this_arg = JS_NULL; - if (argc > 1) - this_arg = argv[1]; - - k = 0; - dir = 1; - end = len; - if (mode == ArrayFindLast || mode == ArrayFindLastIndex) { - k = len - 1; - dir = -1; - end = -1; - } - - // TODO(bnoordhuis) add fast path for fast arrays - for(; k != end; k += dir) { - index_val = JS_NewInt64(ctx, k); - if (JS_IsException(index_val)) - goto exception; - val = JS_GetPropertyValue(ctx, obj, index_val); - if (JS_IsException(val)) - goto exception; - args[0] = val; - args[1] = index_val; - args[2] = this_val; - res = JS_Call(ctx, func, this_arg, 3, args); - if (JS_IsException(res)) - goto exception; - if (JS_ToBoolFree(ctx, res)) { - if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) { - JS_FreeValue(ctx, val); - JS_FreeValue(ctx, obj); - return index_val; - } else { - JS_FreeValue(ctx, index_val); - JS_FreeValue(ctx, obj); - return val; - } - } - JS_FreeValue(ctx, val); - JS_FreeValue(ctx, index_val); - } - JS_FreeValue(ctx, obj); - if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) - return JS_NewInt32(ctx, -1); - else - return JS_NULL; - -exception: - JS_FreeValue(ctx, index_val); - JS_FreeValue(ctx, val); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - static JSValue js_array_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -30219,63 +29731,6 @@ static JSValue js_array_toString(JSContext *ctx, JSValueConst this_val, return ret; } -static JSValue js_array_join(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int toLocaleString) -{ - JSValue obj, sep = JS_NULL, el; - StringBuffer b_s, *b = &b_s; - JSString *p = NULL; - int64_t i, n; - int c; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &n, obj)) - goto exception; - - c = ','; /* default separator */ - if (!toLocaleString && argc > 0 && !JS_IsNull(argv[0])) { - sep = JS_ToString(ctx, argv[0]); - if (JS_IsException(sep)) - goto exception; - p = JS_VALUE_GET_STRING(sep); - if (p->len == 1 && !p->is_wide_char) - c = p->u.str8[0]; - else - c = -1; - } - string_buffer_init(ctx, b, 0); - - for(i = 0; i < n; i++) { - if (i > 0) { - if (c >= 0) { - string_buffer_putc8(b, c); - } else { - string_buffer_concat(b, p, 0, p->len); - } - } - el = JS_GetPropertyUint32(ctx, obj, i); - if (JS_IsException(el)) - goto fail; - if (!JS_IsNull(el) && !JS_IsNull(el)) { - if (toLocaleString) { - el = JS_ToLocaleStringFree(ctx, el); - } - if (string_buffer_concat_value_free(b, el)) - goto fail; - } - } - JS_FreeValue(ctx, sep); - JS_FreeValue(ctx, obj); - return string_buffer_end(b); - -fail: - string_buffer_free(b); - JS_FreeValue(ctx, sep); -exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - static JSValue js_array_pop(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int shift) { @@ -30366,130 +29821,6 @@ static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; } -static JSValue js_array_reverse(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj, lval, hval; - JSValue *arrp; - int64_t len, l, h; - int l_present, h_present; - uint32_t count32; - - lval = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - /* Special case fast arrays */ - if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { - uint32_t ll, hh; - - if (count32 > 1) { - for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) { - lval = arrp[ll]; - arrp[ll] = arrp[hh]; - arrp[hh] = lval; - } - } - return obj; - } - - for (l = 0, h = len - 1; l < h; l++, h--) { - l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval); - if (l_present < 0) - goto exception; - h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval); - if (h_present < 0) - goto exception; - if (h_present) { - if (JS_SetPropertyInt64(ctx, obj, l, hval) < 0) - goto exception; - - if (l_present) { - if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) { - lval = JS_NULL; - goto exception; - } - lval = JS_NULL; - } else { - if (JS_DeletePropertyInt64(ctx, obj, h, JS_PROP_THROW) < 0) - goto exception; - } - } else { - if (l_present) { - if (JS_DeletePropertyInt64(ctx, obj, l, JS_PROP_THROW) < 0) - goto exception; - if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) { - lval = JS_NULL; - goto exception; - } - lval = JS_NULL; - } - } - } - return obj; - - exception: - JS_FreeValue(ctx, lval); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -// Note: a.toReversed() is a.slice().reverse() with the twist that a.slice() -// leaves holes in sparse arrays intact whereas a.toReversed() replaces them -// with undefined, thus in effect creating a dense array. -// Does not use Array[@@species], always returns a base Array. -static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue arr, obj, ret, *arrp, *pval; - JSObject *p; - int64_t i, len; - uint32_t count32; - - ret = JS_EXCEPTION; - arr = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - arr = js_allocate_fast_array(ctx, len); - if (JS_IsException(arr)) - goto exception; - - if (len > 0) { - p = JS_VALUE_GET_OBJ(arr); - - i = len - 1; - pval = p->u.array.u.values; - if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { - for (; i >= 0; i--, pval++) - *pval = JS_DupValue(ctx, arrp[i]); - } else { - // Query order is observable; test262 expects descending order. - for (; i >= 0; i--, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - // Exception; initialize remaining elements. - for (; i >= 0; i--, pval++) - *pval = JS_NULL; - goto exception; - } - } - } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; - } - - ret = arr; - arr = JS_NULL; - -exception: - JS_FreeValue(ctx, arr); - JS_FreeValue(ctx, obj); - return ret; -} - static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int splice) { @@ -30597,127 +29928,6 @@ static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; } -static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue arr, obj, ret, *arrp, *pval, *last; - JSObject *p; - int64_t i, j, len, newlen, start, add, del; - uint32_t count32; - - pval = NULL; - last = NULL; - ret = JS_EXCEPTION; - arr = JS_NULL; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - start = 0; - if (argc > 0) - if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) - goto exception; - - del = 0; - if (argc > 0) - del = len - start; - if (argc > 1) - if (JS_ToInt64Clamp(ctx, &del, argv[1], 0, del, 0)) - goto exception; - - add = 0; - if (argc > 2) - add = argc - 2; - - newlen = len + add - del; - if (newlen > MAX_SAFE_INTEGER) { - JS_ThrowTypeError(ctx, "invalid array length"); - goto exception; - } - - arr = js_allocate_fast_array(ctx, newlen); - if (JS_IsException(arr)) - goto exception; - - if (newlen <= 0) - goto done; - - p = JS_VALUE_GET_OBJ(arr); - pval = &p->u.array.u.values[0]; - last = &p->u.array.u.values[newlen]; - - if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { - for (i = 0; i < start; i++, pval++) - *pval = JS_DupValue(ctx, arrp[i]); - for (j = 0; j < add; j++, pval++) - *pval = JS_DupValue(ctx, argv[2 + j]); - for (i += del; i < len; i++, pval++) - *pval = JS_DupValue(ctx, arrp[i]); - } else { - for (i = 0; i < start; i++, pval++) - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) - goto exception; - for (j = 0; j < add; j++, pval++) - *pval = JS_DupValue(ctx, argv[2 + j]); - for (i += del; i < len; i++, pval++) - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) - goto exception; - } - - assert(pval == last); - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, newlen)) < 0) - goto exception; - -done: - ret = arr; - arr = JS_NULL; - -exception: - while (pval != last) - *pval++ = JS_NULL; - - JS_FreeValue(ctx, arr); - JS_FreeValue(ctx, obj); - return ret; -} - -static JSValue js_array_copyWithin(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj; - int64_t len, from, to, final, count; - - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len)) - goto exception; - - if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len)) - goto exception; - - final = len; - if (argc > 2 && !JS_IsNull(argv[2])) { - if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len)) - goto exception; - } - - count = min_int64(final - from, len - to); - - if (JS_CopySubArray(ctx, obj, to, from, count, - (from < to && to < from + count) ? -1 : +1)) - goto exception; - - return obj; - - exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - static int64_t JS_FlattenIntoArray(JSContext *ctx, JSValueConst target, JSValueConst source, int64_t sourceLen, int64_t targetIndex, int depth, @@ -30824,230 +30034,6 @@ exception: return JS_EXCEPTION; } -/* Array sort */ - -typedef struct ValueSlot { - JSValue val; - JSString *str; - int64_t pos; -} ValueSlot; - -struct array_sort_context { - JSContext *ctx; - int exception; - int has_method; - JSValueConst method; -}; - -static int js_array_cmp_generic(const void *a, const void *b, void *opaque) { - struct array_sort_context *psc = opaque; - JSContext *ctx = psc->ctx; - JSValueConst argv[2]; - JSValue res; - ValueSlot *ap = (ValueSlot *)(void *)a; - ValueSlot *bp = (ValueSlot *)(void *)b; - int cmp; - - if (psc->exception) - return 0; - - if (psc->has_method) { - /* custom sort function is specified as returning 0 for identical - * objects: avoid method call overhead. - */ - if (!memcmp(&ap->val, &bp->val, sizeof(ap->val))) - goto cmp_same; - argv[0] = ap->val; - argv[1] = bp->val; - res = JS_Call(ctx, psc->method, JS_NULL, 2, argv); - if (JS_IsException(res)) - goto exception; - if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) { - int val = JS_VALUE_GET_INT(res); - cmp = (val > 0) - (val < 0); - } else { - double val; - if (JS_ToFloat64Free(ctx, &val, res) < 0) - goto exception; - cmp = (val > 0) - (val < 0); - } - } else { - /* Not supposed to bypass ToString even for identical objects as - * tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.js - */ - if (!ap->str) { - JSValue str = JS_ToString(ctx, ap->val); - if (JS_IsException(str)) - goto exception; - ap->str = JS_VALUE_GET_STRING(str); - } - if (!bp->str) { - JSValue str = JS_ToString(ctx, bp->val); - if (JS_IsException(str)) - goto exception; - bp->str = JS_VALUE_GET_STRING(str); - } - cmp = js_string_compare(ctx, ap->str, bp->str); - } - if (cmp != 0) - return cmp; -cmp_same: - /* make sort stable: compare array offsets */ - return (ap->pos > bp->pos) - (ap->pos < bp->pos); - -exception: - psc->exception = 1; - return 0; -} - -static JSValue js_array_sort(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - struct array_sort_context asc = { ctx, 0, 0, argv[0] }; - JSValue obj = JS_NULL; - ValueSlot *array = NULL; - size_t array_size = 0, pos = 0, n = 0; - int64_t i, len, undefined_count = 0; - int present; - - if (!JS_IsNull(asc.method)) { - if (check_function(ctx, asc.method)) - goto exception; - asc.has_method = 1; - } - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - /* XXX: should special case fast arrays */ - for (i = 0; i < len; i++) { - if (pos >= array_size) { - size_t new_size, slack; - ValueSlot *new_array; - new_size = (array_size + (array_size >> 1) + 31) & ~15; - new_array = js_realloc2(ctx, array, new_size * sizeof(*array), &slack); - if (new_array == NULL) - goto exception; - new_size += slack / sizeof(*new_array); - array = new_array; - array_size = new_size; - } - present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val); - if (present < 0) - goto exception; - if (present == 0) - continue; - if (JS_IsNull(array[pos].val)) { - undefined_count++; - continue; - } - array[pos].str = NULL; - array[pos].pos = i; - pos++; - } - rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc); - if (asc.exception) - goto exception; - - /* XXX: should special case fast arrays */ - while (n < pos) { - if (array[n].str) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str)); - if (array[n].pos == n) { - JS_FreeValue(ctx, array[n].val); - } else { - if (JS_SetPropertyInt64(ctx, obj, n, array[n].val) < 0) { - n++; - goto exception; - } - } - n++; - } - js_free(ctx, array); - for (i = n; undefined_count-- > 0; i++) { - if (JS_SetPropertyInt64(ctx, obj, i, JS_NULL) < 0) - goto fail; - } - for (; i < len; i++) { - if (JS_DeletePropertyInt64(ctx, obj, i, JS_PROP_THROW) < 0) - goto fail; - } - return obj; - -exception: - for (; n < pos; n++) { - JS_FreeValue(ctx, array[n].val); - if (array[n].str) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str)); - } - js_free(ctx, array); -fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -// Note: a.toSorted() is a.slice().sort() with the twist that a.slice() -// leaves holes in sparse arrays intact whereas a.toSorted() replaces them -// with undefined, thus in effect creating a dense array. -// Does not use Array[@@species], always returns a base Array. -static JSValue js_array_toSorted(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue arr, obj, ret, *arrp, *pval; - JSObject *p; - int64_t i, len; - uint32_t count32; - int ok; - - ok = JS_IsNull(argv[0]) || JS_IsFunction(ctx, argv[0]); - if (!ok) - return JS_ThrowTypeError(ctx, "not a function"); - - ret = JS_EXCEPTION; - arr = JS_NULL; - obj = JS_ToObject(ctx, this_val); - if (js_get_length64(ctx, &len, obj)) - goto exception; - - arr = js_allocate_fast_array(ctx, len); - if (JS_IsException(arr)) - goto exception; - - if (len > 0) { - p = JS_VALUE_GET_OBJ(arr); - i = 0; - pval = p->u.array.u.values; - if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { - for (; i < len; i++, pval++) - *pval = JS_DupValue(ctx, arrp[i]); - } else { - for (; i < len; i++, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - for (; i < len; i++, pval++) - *pval = JS_NULL; - goto exception; - } - } - } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; - } - - ret = js_array_sort(ctx, arr, argc, argv); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, ret); - - ret = arr; - arr = JS_NULL; - -exception: - JS_FreeValue(ctx, arr); - JS_FreeValue(ctx, obj); - return ret; -} - typedef struct JSArrayIteratorData { JSValue obj; JSIteratorKindEnum kind; @@ -31191,39 +30177,14 @@ static const JSCFunctionListEntry js_iterator_proto_funcs[] = { }; static const JSCFunctionListEntry js_array_proto_funcs[] = { - JS_CFUNC_DEF("at", 1, js_array_at ), - JS_CFUNC_DEF("with", 2, js_array_with ), JS_CFUNC_DEF("concat", 1, js_array_concat ), - JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ), - JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ), - JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach ), - JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map ), - JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter ), - JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ), - JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ), - JS_CFUNC_DEF("fill", 1, js_array_fill ), - JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, ArrayFind ), - JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, ArrayFindIndex ), - JS_CFUNC_MAGIC_DEF("findLast", 1, js_array_find, ArrayFindLast ), - JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_array_find, ArrayFindLastIndex ), - JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ), - JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ), - JS_CFUNC_DEF("includes", 1, js_array_includes ), - JS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0 ), JS_CFUNC_DEF("toString", 0, js_array_toString ), - JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1 ), JS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0 ), JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ), JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ), JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), - JS_CFUNC_DEF("reverse", 0, js_array_reverse ), - JS_CFUNC_DEF("toReversed", 0, js_array_toReversed ), - JS_CFUNC_DEF("sort", 1, js_array_sort ), - JS_CFUNC_DEF("toSorted", 1, js_array_toSorted ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ), JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ), - JS_CFUNC_DEF("toSpliced", 2, js_array_toSpliced ), - JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ), JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ), JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE ), @@ -31375,205 +30336,6 @@ static JSValue js_thisStringValue(JSContext *ctx, JSValueConst this_val) return JS_ThrowTypeError(ctx, "not a string"); } -static JSValue js_string_fromCharCode(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int i; - StringBuffer b_s, *b = &b_s; - - string_buffer_init(ctx, b, argc); - - for(i = 0; i < argc; i++) { - int32_t c; - if (JS_ToInt32(ctx, &c, argv[i]) || string_buffer_putc16(b, c & 0xffff)) { - string_buffer_free(b); - return JS_EXCEPTION; - } - } - return string_buffer_end(b); -} - -static JSValue js_string_fromCodePoint(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - double d; - int i, c; - StringBuffer b_s, *b = &b_s; - - /* XXX: could pre-compute string length if all arguments are JS_TAG_INT */ - - if (string_buffer_init(ctx, b, argc)) - goto fail; - for(i = 0; i < argc; i++) { - if (JS_VALUE_GET_TAG(argv[i]) == JS_TAG_INT) { - c = JS_VALUE_GET_INT(argv[i]); - if (c < 0 || c > 0x10ffff) - goto range_error; - } else { - if (JS_ToFloat64(ctx, &d, argv[i])) - goto fail; - if (isnan(d) || d < 0 || d > 0x10ffff || (c = (int)d) != d) - goto range_error; - } - if (string_buffer_putc(b, c)) - goto fail; - } - return string_buffer_end(b); - - range_error: - JS_ThrowRangeError(ctx, "invalid code point"); - fail: - string_buffer_free(b); - return JS_EXCEPTION; -} - -static JSValue js_string_raw(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - // raw(temp,...a) - JSValue cooked, val, raw; - StringBuffer b_s, *b = &b_s; - int64_t i, n; - - string_buffer_init(ctx, b, 0); - raw = JS_NULL; - cooked = JS_ToObject(ctx, argv[0]); - if (JS_IsException(cooked)) - goto exception; - raw = JS_ToObjectFree(ctx, JS_GetProperty(ctx, cooked, JS_ATOM_raw)); - if (JS_IsException(raw)) - goto exception; - if (js_get_length64(ctx, &n, raw) < 0) - goto exception; - - for (i = 0; i < n; i++) { - val = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, raw, i)); - if (JS_IsException(val)) - goto exception; - string_buffer_concat_value_free(b, val); - if (i < n - 1 && i + 1 < argc) { - if (string_buffer_concat_value(b, argv[i + 1])) - goto exception; - } - } - JS_FreeValue(ctx, cooked); - JS_FreeValue(ctx, raw); - return string_buffer_end(b); - -exception: - JS_FreeValue(ctx, cooked); - JS_FreeValue(ctx, raw); - string_buffer_free(b); - return JS_EXCEPTION; -} - -/* only used in test262 */ -JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - uint32_t start, end, i, n; - StringBuffer b_s, *b = &b_s; - - if (JS_ToUint32(ctx, &start, argv[0]) || - JS_ToUint32(ctx, &end, argv[1])) - return JS_EXCEPTION; - end = min_uint32(end, 0x10ffff + 1); - - if (start > end) { - start = end; - } - n = end - start; - if (end > 0x10000) { - n += end - max_uint32(start, 0x10000); - } - if (string_buffer_init2(ctx, b, n, end >= 0x100)) - return JS_EXCEPTION; - for(i = start; i < end; i++) { - string_buffer_putc(b, i); - } - return string_buffer_end(b); -} - -static JSValue js_string_charCodeAt(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - JSString *p; - int idx, c; - - val = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); - if (JS_ToInt32Sat(ctx, &idx, argv[0])) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - if (idx < 0 || idx >= p->len) { - ret = JS_NAN; - } else { - c = string_get(p, idx); - ret = JS_NewInt32(ctx, c); - } - JS_FreeValue(ctx, val); - return ret; -} - -static JSValue js_string_charAt(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int is_at) -{ - JSValue val, ret; - JSString *p; - int idx, c; - - val = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); - if (JS_ToInt32Sat(ctx, &idx, argv[0])) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - if (idx < 0 && is_at) - idx += p->len; - if (idx < 0 || idx >= p->len) { - if (is_at) - ret = JS_NULL; - else - ret = JS_AtomToString(ctx, JS_ATOM_empty_string); - } else { - c = string_get(p, idx); - ret = js_new_string_char(ctx, c); - } - JS_FreeValue(ctx, val); - return ret; -} - -static JSValue js_string_codePointAt(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue val, ret; - JSString *p; - int idx, c; - - val = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); - if (JS_ToInt32Sat(ctx, &idx, argv[0])) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - if (idx < 0 || idx >= p->len) { - ret = JS_NULL; - } else { - c = string_getc(p, &idx); - ret = JS_NewInt32(ctx, c); - } - JS_FreeValue(ctx, val); - return ret; -} - static JSValue js_string_concat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -31671,59 +30433,6 @@ static int js_string_find_invalid_codepoint(JSString *p) return -1; } -static JSValue js_string_isWellFormed(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue str; - JSString *p; - BOOL ret; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return JS_EXCEPTION; - p = JS_VALUE_GET_STRING(str); - ret = (js_string_find_invalid_codepoint(p) < 0); - JS_FreeValue(ctx, str); - return JS_NewBool(ctx, ret); -} - -static JSValue js_string_toWellFormed(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue str, ret; - JSString *p; - int i; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return JS_EXCEPTION; - - p = JS_VALUE_GET_STRING(str); - /* avoid reallocating the string if it is well-formed */ - i = js_string_find_invalid_codepoint(p); - if (i < 0) - return str; - - ret = js_new_string16_len(ctx, p->u.str16, p->len); - JS_FreeValue(ctx, str); - if (JS_IsException(ret)) - return JS_EXCEPTION; - - p = JS_VALUE_GET_STRING(ret); - for (; i < p->len; i++) { - uint32_t c = p->u.str16[i]; - if (is_surrogate(c)) { - if (is_hi_surrogate(c) && (i + 1) < p->len - && is_lo_surrogate(p->u.str16[i + 1])) { - i++; - } else { - p->u.str16[i] = 0xFFFD; - } - } - } - return ret; -} - static JSValue js_string_indexOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int lastIndexOf) { @@ -31792,70 +30501,6 @@ fail: /* return < 0 if exception or TRUE/FALSE */ static int js_is_regexp(JSContext *ctx, JSValueConst obj); -static JSValue js_string_includes(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - JSValue str, v = JS_NULL; - int i, len, v_len, pos, start, stop, ret; - JSString *p; - JSString *p1; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return str; - ret = js_is_regexp(ctx, argv[0]); - if (ret) { - if (ret > 0) - JS_ThrowTypeError(ctx, "regexp not supported"); - goto fail; - } - v = JS_ToString(ctx, argv[0]); - if (JS_IsException(v)) - goto fail; - p = JS_VALUE_GET_STRING(str); - p1 = JS_VALUE_GET_STRING(v); - len = p->len; - v_len = p1->len; - pos = (magic == 2) ? len : 0; - if (argc > 1 && !JS_IsNull(argv[1])) { - if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) - goto fail; - } - len -= v_len; - ret = 0; - if (magic == 0) { - start = pos; - stop = len; - } else { - if (magic == 1) { - if (pos > len) - goto done; - } else { - pos -= v_len; - } - start = stop = pos; - } - if (start >= 0 && start <= stop) { - for (i = start;; i++) { - if (!string_cmp(p, p1, i, 0, v_len)) { - ret = 1; - break; - } - if (i == stop) - break; - } - } - done: - JS_FreeValue(ctx, str); - JS_FreeValue(ctx, v); - return JS_NewBool(ctx, ret); - -fail: - JS_FreeValue(ctx, str); - JS_FreeValue(ctx, v); - return JS_EXCEPTION; -} - static int check_regexp_g_flag(JSContext *ctx, JSValueConst regexp) { int ret; @@ -32156,334 +30801,6 @@ exception: return JS_EXCEPTION; } -static JSValue js_string_split(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - // split(sep, limit) - JSValueConst O = this_val, separator = argv[0], limit = argv[1]; - JSValueConst args[2]; - JSValue S, A, R, T; - uint32_t lim, lengthA; - int64_t p, q, s, r, e; - JSString *sp, *rp; - - if (JS_IsNull(O) || JS_IsNull(O)) - return JS_ThrowTypeError(ctx, "cannot convert to object"); - - S = JS_NULL; - A = JS_NULL; - R = JS_NULL; - - if (!JS_IsNull(separator) && !JS_IsNull(separator)) { - JSValue splitter; - splitter = JS_GetProperty(ctx, separator, JS_ATOM_Symbol_split); - if (JS_IsException(splitter)) - return JS_EXCEPTION; - if (!JS_IsNull(splitter) && !JS_IsNull(splitter)) { - args[0] = O; - args[1] = limit; - return JS_CallFree(ctx, splitter, separator, 2, args); - } - } - S = JS_ToString(ctx, O); - if (JS_IsException(S)) - goto exception; - A = JS_NewArray(ctx); - if (JS_IsException(A)) - goto exception; - lengthA = 0; - if (JS_IsNull(limit)) { - lim = 0xffffffff; - } else { - if (JS_ToUint32(ctx, &lim, limit) < 0) - goto exception; - } - sp = JS_VALUE_GET_STRING(S); - s = sp->len; - R = JS_ToString(ctx, separator); - if (JS_IsException(R)) - goto exception; - rp = JS_VALUE_GET_STRING(R); - r = rp->len; - p = 0; - if (lim == 0) - goto done; - if (JS_IsNull(separator)) - goto add_tail; - if (s == 0) { - if (r != 0) - goto add_tail; - goto done; - } - q = p; - for (q = p; (q += !r) <= s - r - !r; q = p = e + r) { - e = string_indexof(sp, rp, q); - if (e < 0) - break; - T = js_sub_string(ctx, sp, p, e); - if (JS_IsException(T)) - goto exception; - if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0) - goto exception; - if (lengthA == lim) - goto done; - } -add_tail: - T = js_sub_string(ctx, sp, p, s); - if (JS_IsException(T)) - goto exception; - if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T,0 ) < 0) - goto exception; -done: - JS_FreeValue(ctx, S); - JS_FreeValue(ctx, R); - return A; - -exception: - JS_FreeValue(ctx, A); - JS_FreeValue(ctx, S); - JS_FreeValue(ctx, R); - return JS_EXCEPTION; -} - -static JSValue js_string_substring(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue str, ret; - int a, b, start, end; - JSString *p; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return str; - p = JS_VALUE_GET_STRING(str); - if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, p->len, 0)) { - JS_FreeValue(ctx, str); - return JS_EXCEPTION; - } - b = p->len; - if (!JS_IsNull(argv[1])) { - if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, p->len, 0)) { - JS_FreeValue(ctx, str); - return JS_EXCEPTION; - } - } - if (a < b) { - start = a; - end = b; - } else { - start = b; - end = a; - } - ret = js_sub_string(ctx, p, start, end); - JS_FreeValue(ctx, str); - return ret; -} - -static JSValue js_string_substr(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue str, ret; - int a, len, n; - JSString *p; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return str; - p = JS_VALUE_GET_STRING(str); - len = p->len; - if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, len)) { - JS_FreeValue(ctx, str); - return JS_EXCEPTION; - } - n = len - a; - if (!JS_IsNull(argv[1])) { - if (JS_ToInt32Clamp(ctx, &n, argv[1], 0, len - a, 0)) { - JS_FreeValue(ctx, str); - return JS_EXCEPTION; - } - } - ret = js_sub_string(ctx, p, a, a + n); - JS_FreeValue(ctx, str); - return ret; -} - -static JSValue js_string_slice(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue str, ret; - int len, start, end; - JSString *p; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return str; - p = JS_VALUE_GET_STRING(str); - len = p->len; - if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) { - JS_FreeValue(ctx, str); - return JS_EXCEPTION; - } - end = len; - if (!JS_IsNull(argv[1])) { - if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) { - JS_FreeValue(ctx, str); - return JS_EXCEPTION; - } - } - ret = js_sub_string(ctx, p, start, max_int(end, start)); - JS_FreeValue(ctx, str); - return ret; -} - -static JSValue js_string_pad(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int padEnd) -{ - JSValue str, v = JS_NULL; - StringBuffer b_s, *b = &b_s; - JSString *p, *p1 = NULL; - int n, len, c = ' '; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - goto fail1; - if (JS_ToInt32Sat(ctx, &n, argv[0])) - goto fail2; - p = JS_VALUE_GET_STRING(str); - len = p->len; - if (len >= n) - return str; - if (argc > 1 && !JS_IsNull(argv[1])) { - v = JS_ToString(ctx, argv[1]); - if (JS_IsException(v)) - goto fail2; - p1 = JS_VALUE_GET_STRING(v); - if (p1->len == 0) { - JS_FreeValue(ctx, v); - return str; - } - if (p1->len == 1) { - c = string_get(p1, 0); - p1 = NULL; - } - } - if (n > JS_STRING_LEN_MAX) { - JS_ThrowRangeError(ctx, "invalid string length"); - goto fail3; - } - if (string_buffer_init(ctx, b, n)) - goto fail3; - n -= len; - if (padEnd) { - if (string_buffer_concat(b, p, 0, len)) - goto fail; - } - if (p1) { - while (n > 0) { - int chunk = min_int(n, p1->len); - if (string_buffer_concat(b, p1, 0, chunk)) - goto fail; - n -= chunk; - } - } else { - if (string_buffer_fill(b, c, n)) - goto fail; - } - if (!padEnd) { - if (string_buffer_concat(b, p, 0, len)) - goto fail; - } - JS_FreeValue(ctx, v); - JS_FreeValue(ctx, str); - return string_buffer_end(b); - -fail: - string_buffer_free(b); -fail3: - JS_FreeValue(ctx, v); -fail2: - JS_FreeValue(ctx, str); -fail1: - return JS_EXCEPTION; -} - -static JSValue js_string_repeat(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue str; - StringBuffer b_s, *b = &b_s; - JSString *p; - int64_t val; - int n, len; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - goto fail; - if (JS_ToInt64Sat(ctx, &val, argv[0])) - goto fail; - if (val < 0 || val > 2147483647) { - JS_ThrowRangeError(ctx, "invalid repeat count"); - goto fail; - } - n = val; - p = JS_VALUE_GET_STRING(str); - len = p->len; - if (len == 0 || n == 1) - return str; - // XXX: potential arithmetic overflow - if (val * len > JS_STRING_LEN_MAX) { - JS_ThrowRangeError(ctx, "invalid string length"); - goto fail; - } - if (string_buffer_init2(ctx, b, n * len, p->is_wide_char)) - goto fail; - if (len == 1) { - string_buffer_fill(b, string_get(p, 0), n); - } else { - while (n-- > 0) { - string_buffer_concat(b, p, 0, len); - } - } - JS_FreeValue(ctx, str); - return string_buffer_end(b); - -fail: - JS_FreeValue(ctx, str); - return JS_EXCEPTION; -} - -static JSValue js_string_trim(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - JSValue str, ret; - int a, b, len; - JSString *p; - - str = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(str)) - return str; - p = JS_VALUE_GET_STRING(str); - a = 0; - b = len = p->len; - if (magic & 1) { - while (a < len && lre_is_space(string_get(p, a))) - a++; - } - if (magic & 2) { - while (b > a && lre_is_space(string_get(p, b - 1))) - b--; - } - ret = js_sub_string(ctx, p, a, b); - JS_FreeValue(ctx, str); - return ret; -} - -static JSValue js_string___quote(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_ToQuotedString(ctx, this_val); -} - /* return 0 if before the first char */ static int string_prevc(JSString *p, int *pidx) { @@ -32537,239 +30854,6 @@ static BOOL test_final_sigma(JSString *p, int sigma_pos) return !lre_is_cased(c1); } -static JSValue js_string_toLowerCase(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int to_lower) -{ - JSValue val; - StringBuffer b_s, *b = &b_s; - JSString *p; - int i, c, j, l; - uint32_t res[LRE_CC_RES_LEN_MAX]; - - val = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); - if (p->len == 0) - return val; - if (string_buffer_init(ctx, b, p->len)) - goto fail; - for(i = 0; i < p->len;) { - c = string_getc(p, &i); - if (c == 0x3a3 && to_lower && test_final_sigma(p, i - 1)) { - res[0] = 0x3c2; /* final sigma */ - l = 1; - } else { - l = lre_case_conv(res, c, to_lower); - } - for(j = 0; j < l; j++) { - if (string_buffer_putc(b, res[j])) - goto fail; - } - } - JS_FreeValue(ctx, val); - return string_buffer_end(b); - fail: - JS_FreeValue(ctx, val); - string_buffer_free(b); - return JS_EXCEPTION; -} - -#ifdef CONFIG_ALL_UNICODE - -/* return (-1, NULL) if exception, otherwise (len, buf) */ -static int JS_ToUTF32String(JSContext *ctx, uint32_t **pbuf, JSValueConst val1) -{ - JSValue val; - JSString *p; - uint32_t *buf; - int i, j, len; - - val = JS_ToString(ctx, val1); - if (JS_IsException(val)) - return -1; - p = JS_VALUE_GET_STRING(val); - len = p->len; - /* UTF32 buffer length is len minus the number of correct surrogates pairs */ - buf = js_malloc(ctx, sizeof(buf[0]) * max_int(len, 1)); - if (!buf) { - JS_FreeValue(ctx, val); - goto fail; - } - for(i = j = 0; i < len;) - buf[j++] = string_getc(p, &i); - JS_FreeValue(ctx, val); - *pbuf = buf; - return j; - fail: - *pbuf = NULL; - return -1; -} - -static JSValue JS_NewUTF32String(JSContext *ctx, const uint32_t *buf, int len) -{ - int i; - StringBuffer b_s, *b = &b_s; - if (string_buffer_init(ctx, b, len)) - return JS_EXCEPTION; - for(i = 0; i < len; i++) { - if (string_buffer_putc(b, buf[i])) - goto fail; - } - return string_buffer_end(b); - fail: - string_buffer_free(b); - return JS_EXCEPTION; -} - -static int js_string_normalize1(JSContext *ctx, uint32_t **pout_buf, - JSValueConst val, - UnicodeNormalizationEnum n_type) -{ - int buf_len, out_len; - uint32_t *buf, *out_buf; - - buf_len = JS_ToUTF32String(ctx, &buf, val); - if (buf_len < 0) - return -1; - out_len = unicode_normalize(&out_buf, buf, buf_len, n_type, - ctx->rt, (DynBufReallocFunc *)js_realloc_rt); - js_free(ctx, buf); - if (out_len < 0) - return -1; - *pout_buf = out_buf; - return out_len; -} - -static JSValue js_string_normalize(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *form, *p; - size_t form_len; - int is_compat, out_len; - UnicodeNormalizationEnum n_type; - JSValue val; - uint32_t *out_buf; - - val = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(val)) - return val; - - if (argc == 0 || JS_IsNull(argv[0])) { - n_type = UNICODE_NFC; - } else { - form = JS_ToCStringLen(ctx, &form_len, argv[0]); - if (!form) - goto fail1; - p = form; - if (p[0] != 'N' || p[1] != 'F') - goto bad_form; - p += 2; - is_compat = FALSE; - if (*p == 'K') { - is_compat = TRUE; - p++; - } - if (*p == 'C' || *p == 'D') { - n_type = UNICODE_NFC + is_compat * 2 + (*p - 'C'); - if ((p + 1 - form) != form_len) - goto bad_form; - } else { - bad_form: - JS_FreeCString(ctx, form); - JS_ThrowRangeError(ctx, "bad normalization form"); - fail1: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - JS_FreeCString(ctx, form); - } - - out_len = js_string_normalize1(ctx, &out_buf, val, n_type); - JS_FreeValue(ctx, val); - if (out_len < 0) - return JS_EXCEPTION; - val = JS_NewUTF32String(ctx, out_buf, out_len); - js_free(ctx, out_buf); - return val; -} - -/* return < 0, 0 or > 0 */ -static int js_UTF32_compare(const uint32_t *buf1, int buf1_len, - const uint32_t *buf2, int buf2_len) -{ - int i, len, c, res; - len = min_int(buf1_len, buf2_len); - for(i = 0; i < len; i++) { - /* Note: range is limited so a subtraction is valid */ - c = buf1[i] - buf2[i]; - if (c != 0) - return c; - } - if (buf1_len == buf2_len) - res = 0; - else if (buf1_len < buf2_len) - res = -1; - else - res = 1; - return res; -} - -static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue a, b; - int cmp, a_len, b_len; - uint32_t *a_buf, *b_buf; - - a = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(a)) - return JS_EXCEPTION; - b = JS_ToString(ctx, argv[0]); - if (JS_IsException(b)) { - JS_FreeValue(ctx, a); - return JS_EXCEPTION; - } - a_len = js_string_normalize1(ctx, &a_buf, a, UNICODE_NFC); - JS_FreeValue(ctx, a); - if (a_len < 0) { - JS_FreeValue(ctx, b); - return JS_EXCEPTION; - } - - b_len = js_string_normalize1(ctx, &b_buf, b, UNICODE_NFC); - JS_FreeValue(ctx, b); - if (b_len < 0) { - js_free(ctx, a_buf); - return JS_EXCEPTION; - } - cmp = js_UTF32_compare(a_buf, a_len, b_buf, b_len); - js_free(ctx, a_buf); - js_free(ctx, b_buf); - return JS_NewInt32(ctx, cmp); -} -#else /* CONFIG_ALL_UNICODE */ -static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue a, b; - int cmp; - - a = JS_ToStringCheckObject(ctx, this_val); - if (JS_IsException(a)) - return JS_EXCEPTION; - b = JS_ToString(ctx, argv[0]); - if (JS_IsException(b)) { - JS_FreeValue(ctx, a); - return JS_EXCEPTION; - } - cmp = js_string_compare(ctx, JS_VALUE_GET_STRING(a), JS_VALUE_GET_STRING(b)); - JS_FreeValue(ctx, a); - JS_FreeValue(ctx, b); - return JS_NewInt32(ctx, cmp); -} -#endif /* !CONFIG_ALL_UNICODE */ - /* also used for String.prototype.valueOf */ static JSValue js_string_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -32815,65 +30899,16 @@ static JSValue js_string_iterator_next(JSContext *ctx, JSValueConst this_val, } } -/* ES6 Annex B 2.3.2 etc. */ -enum { - magic_string_anchor, - magic_string_big, - magic_string_blink, - magic_string_bold, - magic_string_fixed, - magic_string_fontcolor, - magic_string_fontsize, - magic_string_italics, - magic_string_link, - magic_string_small, - magic_string_strike, - magic_string_sub, - magic_string_sup, -}; - -static const JSCFunctionListEntry js_string_funcs[] = { - JS_CFUNC_DEF("fromCharCode", 1, js_string_fromCharCode ), - JS_CFUNC_DEF("fromCodePoint", 1, js_string_fromCodePoint ), - JS_CFUNC_DEF("raw", 1, js_string_raw ), -}; - static const JSCFunctionListEntry js_string_proto_funcs[] = { JS_PROP_INT32_DEF("length", 0, JS_PROP_CONFIGURABLE ), - JS_CFUNC_MAGIC_DEF("at", 1, js_string_charAt, 1 ), - JS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt ), - JS_CFUNC_MAGIC_DEF("charAt", 1, js_string_charAt, 0 ), JS_CFUNC_DEF("concat", 1, js_string_concat ), - JS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt ), - JS_CFUNC_DEF("isWellFormed", 0, js_string_isWellFormed ), - JS_CFUNC_DEF("toWellFormed", 0, js_string_toWellFormed ), JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), - JS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0 ), - JS_CFUNC_MAGIC_DEF("endsWith", 1, js_string_includes, 2 ), - JS_CFUNC_MAGIC_DEF("startsWith", 1, js_string_includes, 1 ), JS_CFUNC_MAGIC_DEF("match", 1, js_string_match, JS_ATOM_Symbol_match ), - JS_CFUNC_MAGIC_DEF("matchAll", 1, js_string_match, JS_ATOM_Symbol_matchAll ), - JS_CFUNC_MAGIC_DEF("search", 1, js_string_match, JS_ATOM_Symbol_search ), - JS_CFUNC_DEF("split", 2, js_string_split ), - JS_CFUNC_DEF("substring", 2, js_string_substring ), - JS_CFUNC_DEF("substr", 2, js_string_substr ), - JS_CFUNC_DEF("slice", 2, js_string_slice ), - JS_CFUNC_DEF("repeat", 1, js_string_repeat ), JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ), JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ), - JS_CFUNC_MAGIC_DEF("padEnd", 1, js_string_pad, 1 ), - JS_CFUNC_MAGIC_DEF("padStart", 1, js_string_pad, 0 ), - JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ), - JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ), - JS_ALIAS_DEF("trimRight", "trimEnd" ), - JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ), - JS_ALIAS_DEF("trimLeft", "trimStart" ), JS_CFUNC_DEF("toString", 0, js_string_toString ), JS_CFUNC_DEF("valueOf", 0, js_string_toString ), - JS_CFUNC_DEF("__quote", 1, js_string___quote ), - JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), - JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), JS_CFUNC_MAGIC_DEF("[Symbol.iterator]", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE | 4 ), }; @@ -32882,19 +30917,6 @@ static const JSCFunctionListEntry js_string_iterator_proto_funcs[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "String Iterator", JS_PROP_CONFIGURABLE ), }; -static const JSCFunctionListEntry js_string_proto_normalize[] = { -#ifdef CONFIG_ALL_UNICODE - JS_CFUNC_DEF("normalize", 0, js_string_normalize ), -#endif - JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ), -}; - -void JS_AddIntrinsicStringNormalize(JSContext *ctx) -{ - JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_normalize, - countof(js_string_proto_normalize)); -} - /* RegExp */ static void js_regexp_finalizer(JSRuntime *rt, JSValue val) @@ -35892,6 +33914,175 @@ static JSValue js_cell_format_number(JSContext *ctx, double num, const char *for /* Forward declaration for blob helper */ static blob *js_get_blob(JSContext *ctx, JSValueConst val); +/* modulo(dividend, divisor) - result has sign of divisor */ +static JSValue js_cell_modulo(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 2) + return JS_NULL; + + double dividend, divisor; + if (JS_ToFloat64(ctx, ÷nd, argv[0])) + return JS_NULL; + if (JS_ToFloat64(ctx, &divisor, argv[1])) + return JS_NULL; + + /* If either operand is NaN, return null */ + if (isnan(dividend) || isnan(divisor)) + return JS_NULL; + + /* If divisor is 0, return null */ + if (divisor == 0) + return JS_NULL; + + /* If dividend is 0, return 0 */ + if (dividend == 0) + return JS_NewFloat64(ctx, 0.0); + + /* modulo = dividend - (divisor * floor(dividend / divisor)) */ + double result = dividend - (divisor * floor(dividend / divisor)); + + return JS_NewFloat64(ctx, result); +} + +/* neg(number) - negate a number */ +static JSValue js_cell_neg(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_NULL; + + double num; + if (JS_ToFloat64(ctx, &num, argv[0])) + return JS_NULL; + + if (isnan(num)) + return JS_NULL; + + return JS_NewFloat64(ctx, -num); +} + +/* normalize(text) - Unicode normalize to NFC form */ +static JSValue js_cell_normalize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_NewString(ctx, ""); + + JSValue str_val = JS_ToString(ctx, argv[0]); + if (JS_IsException(str_val)) + return JS_EXCEPTION; + + /* Get the string as UTF-32 for normalization */ + uint32_t *codepoints = NULL; + size_t len; +// codepoints = JS_ToUTF32String(ctx, &len, str_val); + JS_FreeValue(ctx, str_val); + + if (!codepoints) + return JS_EXCEPTION; + + if (len == 0) { + js_free(ctx, codepoints); + return JS_NewString(ctx, ""); + } + + /* Normalize to NFC */ + uint32_t *normalized = NULL; + int norm_len = unicode_normalize(&normalized, codepoints, len, UNICODE_NFC, + ctx->rt, js_realloc_rt); + js_free(ctx, codepoints); + + if (norm_len < 0 || !normalized) + return JS_EXCEPTION; + + /* Convert back to JSValue string */ +// JSValue result = JS_NewUTF32String(ctx, normalized, norm_len); + js_free_rt(ctx->rt, normalized); + + return JS_NULL; +} + +/* character(value) - get character from text or codepoint */ +static JSValue js_cell_character(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_NewString(ctx, ""); + + JSValue arg = argv[0]; + int tag = JS_VALUE_GET_TAG(arg); + + /* Handle string - return first character */ + if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE) { + JSString *p = JS_VALUE_GET_STRING(arg); + if (p->len == 0) + return JS_NewString(ctx, ""); + + /* Return first character (handle UTF-16 surrogate pairs) */ + if (p->is_wide_char) { + uint32_t c = p->u.str16[0]; + if (is_hi_surrogate(c) && p->len > 1 && is_lo_surrogate(p->u.str16[1])) { + /* Surrogate pair - return both code units */ + return js_sub_string(ctx, p, 0, 2); + } + return js_sub_string(ctx, p, 0, 1); + } else { + return js_sub_string(ctx, p, 0, 1); + } + } + + /* Handle integer - return character from codepoint */ + if (tag == JS_TAG_INT) { + int32_t val = JS_VALUE_GET_INT(arg); + if (val < 0 || val > 0x10FFFF) + return JS_NewString(ctx, ""); + + uint32_t codepoint = (uint32_t)val; + if (codepoint < 0x80) { + char buf[2] = { (char)codepoint, '\0' }; + return JS_NewString(ctx, buf); + } else if (codepoint <= 0xFFFF) { + uint16_t buf[2] = { (uint16_t)codepoint, 0 }; + return js_new_string16_len(ctx, buf, 1); + } else { + /* Encode as surrogate pair */ + codepoint -= 0x10000; + uint16_t buf[3]; + buf[0] = 0xD800 + (codepoint >> 10); + buf[1] = 0xDC00 + (codepoint & 0x3FF); + buf[2] = 0; + return js_new_string16_len(ctx, buf, 2); + } + } + + /* Handle float - convert to integer if non-negative and within range */ + if (tag == JS_TAG_FLOAT64) { + double d = JS_VALUE_GET_FLOAT64(arg); + if (isnan(d) || d < 0 || d > 0x10FFFF || d != trunc(d)) + return JS_NewString(ctx, ""); + + uint32_t codepoint = (uint32_t)d; + if (codepoint < 0x80) { + char buf[2] = { (char)codepoint, '\0' }; + return JS_NewString(ctx, buf); + } else if (codepoint <= 0xFFFF) { + uint16_t buf[2] = { (uint16_t)codepoint, 0 }; + return js_new_string16_len(ctx, buf, 1); + } else { + /* Encode as surrogate pair */ + codepoint -= 0x10000; + uint16_t buf[3]; + buf[0] = 0xD800 + (codepoint >> 10); + buf[1] = 0xDC00 + (codepoint & 0x3FF); + buf[2] = 0; + return js_new_string16_len(ctx, buf, 2); + } + } + + return JS_NewString(ctx, ""); +} + /* text(arg, format) - main text function */ static JSValue js_cell_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -36361,18 +34552,71 @@ static JSValue js_cell_text_search(JSContext *ctx, JSValueConst this_val, if (result == -1) return JS_NULL; return JS_NewInt32(ctx, result); } +/* Helpers (C, not C++). Put these above js_cell_text_replace in the same C file. */ -/* text.replace(str, target, replacement, limit) - replace substrings */ +static int sb_concat_value_to_string_free(JSContext *ctx, StringBuffer *b, JSValue v) +{ + JSValue s = JS_ToString(ctx, v); + JS_FreeValue(ctx, v); + if (JS_IsException(s)) return -1; + if (string_buffer_concat_value_free(b, s)) return -1; + return 0; +} + +/* Build replacement for a match at `found`. + * - If replacement is a function: call it as (match_text, found) + * - Else if replacement exists: duplicate it + * - Else: empty string + * Returns JS_EXCEPTION on error, JS_NULL if callback returned null, or any JSValue. + * This function CONSUMES match_val if it calls a function (it will free it via args cleanup), + * otherwise it will free match_val before returning. + */ +static JSValue make_replacement(JSContext *ctx, int argc, JSValueConst *argv, int found, JSValue match_val) +{ + JSValue rep; + + if (argc > 2 && JS_IsFunction(ctx, argv[2])) { + JSValue args[2]; + args[0] = match_val; + args[1] = JS_NewInt32(ctx, found); + rep = JS_Call(ctx, argv[2], JS_NULL, 2, args); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + return rep; + } + + JS_FreeValue(ctx, match_val); + + if (argc > 2) return JS_DupValue(ctx, argv[2]); + return JS_AtomToString(ctx, JS_ATOM_empty_string); +} + +/* text.replace(text, target, replacement, limit) + * + * Return a new text in which the target is replaced by the replacement. + * + * target: string (pattern support not implemented here; non-string => null) + * replacement: string or function(match_text, start_pos) -> string|null + * limit: max number of replacements (default unlimited). Limit includes null matches. + * + * Empty target semantics: + * Replace at every boundary: before first char, between chars, after last char. + * Example: replace("abc", "", "-") => "-a-b-c-" + * Boundaries count toward limit even if replacement returns null. + */ static JSValue js_cell_text_replace(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; - int tag1 = JS_VALUE_GET_TAG(argv[0]); - int tag2 = JS_VALUE_GET_TAG(argv[1]); - if ((tag1 != JS_TAG_STRING && tag1 != JS_TAG_STRING_ROPE) || - (tag2 != JS_TAG_STRING && tag2 != JS_TAG_STRING_ROPE)) - return JS_NULL; + /* Require text + target be strings (or ropes) */ + { + int tag_text = JS_VALUE_GET_TAG(argv[0]); + int tag_tgt = JS_VALUE_GET_TAG(argv[1]); + if ((tag_text != JS_TAG_STRING && tag_text != JS_TAG_STRING_ROPE) || + (tag_tgt != JS_TAG_STRING && tag_tgt != JS_TAG_STRING_ROPE)) + return JS_NULL; + } JSValue str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; @@ -36386,25 +34630,66 @@ static JSValue js_cell_text_replace(JSContext *ctx, JSValueConst this_val, JSString *sp = JS_VALUE_GET_STRING(str); JSString *tp = JS_VALUE_GET_STRING(target); - int limit = -1; /* -1 means unlimited */ + int32_t limit = -1; /* -1 means unlimited */ if (argc > 3 && !JS_IsNull(argv[3])) { if (JS_ToInt32(ctx, &limit, argv[3])) { JS_FreeValue(ctx, str); JS_FreeValue(ctx, target); return JS_NULL; } + if (limit < 0) limit = -1; } - StringBuffer b_s, *b = &b_s; - string_buffer_init(ctx, b, sp->len); + int len = (int)sp->len; + int t_len = (int)tp->len; + StringBuffer b_s, *b = &b_s; + string_buffer_init(ctx, b, len); + + /* Empty target: boundary replacements */ + if (t_len == 0) { + int32_t count = 0; + + for (int boundary = 0; boundary <= len; boundary++) { + if (limit >= 0 && count >= limit) break; + + /* match text is "" */ + JSValue match = JS_AtomToString(ctx, JS_ATOM_empty_string); + if (JS_IsException(match)) goto fail; + + JSValue rep = make_replacement(ctx, argc, argv, boundary, match); + if (JS_IsException(rep)) goto fail; + + /* Count includes null matches */ + count++; + + if (!JS_IsNull(rep)) { + if (sb_concat_value_to_string_free(ctx, b, rep) < 0) goto fail; + } else { + JS_FreeValue(ctx, rep); + } + + /* Copy next character between boundaries (does not affect count) */ + if (boundary < len) { + JSValue ch = js_sub_string(ctx, sp, boundary, boundary + 1); + if (JS_IsException(ch)) goto fail; + if (string_buffer_concat_value_free(b, ch)) goto fail; + } + } + + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, target); + return string_buffer_end(b); + } + + /* Non-empty target: left-to-right, non-overlapping */ int pos = 0; - int count = 0; - int len = sp->len; - int t_len = tp->len; + int32_t count = 0; while (pos <= len - t_len && (limit < 0 || count < limit)) { int found = -1; + + /* Find next occurrence (naive search) */ for (int i = pos; i <= len - t_len; i++) { if (!string_cmp(sp, tp, i, 0, t_len)) { found = i; @@ -36413,43 +34698,30 @@ static JSValue js_cell_text_replace(JSContext *ctx, JSValueConst this_val, } if (found < 0) break; - /* Copy up to the match */ + /* Copy prefix up to match */ if (found > pos) { JSValue sub = js_sub_string(ctx, sp, pos, found); if (JS_IsException(sub)) goto fail; if (string_buffer_concat_value_free(b, sub)) goto fail; } - /* Get replacement */ - JSValue rep; - if (argc > 2 && JS_IsFunction(ctx, argv[2])) { - JSValue args[2]; - args[0] = JS_NewStringLen(ctx, (const char *)tp->u.str8, t_len); - args[1] = JS_NewInt32(ctx, found); - rep = JS_Call(ctx, argv[2], JS_NULL, 2, args); - JS_FreeValue(ctx, args[0]); - JS_FreeValue(ctx, args[1]); - if (JS_IsException(rep)) goto fail; - if (JS_IsNull(rep)) { - pos = found + t_len; - count++; - continue; - } - } else if (argc > 2) { - rep = JS_DupValue(ctx, argv[2]); - } else { - rep = JS_AtomToString(ctx, JS_ATOM_empty_string); - } + /* Match text for callback */ + JSValue match = js_sub_string(ctx, sp, found, found + t_len); + if (JS_IsException(match)) goto fail; + + JSValue rep = make_replacement(ctx, argc, argv, found, match); + if (JS_IsException(rep)) goto fail; + + /* Count includes null matches */ + count++; if (!JS_IsNull(rep)) { - JSValue rep_str = JS_ToString(ctx, rep); + if (sb_concat_value_to_string_free(ctx, b, rep) < 0) goto fail; + } else { JS_FreeValue(ctx, rep); - if (JS_IsException(rep_str)) goto fail; - if (string_buffer_concat_value_free(b, rep_str)) goto fail; } pos = found + t_len; - count++; } /* Copy remainder */ @@ -36470,6 +34742,7 @@ fail: return JS_EXCEPTION; } + /* ---------------------------------------------------------------------------- * array function and sub-functions * ---------------------------------------------------------------------------- */ @@ -37094,6 +35367,8 @@ static JSValue js_cell_array_sort(JSContext *ctx, JSValueConst this_val, JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; + if (len == 0) return result; + JSValue *items = js_malloc(ctx, sizeof(JSValue) * len); double *keys = js_malloc(ctx, sizeof(double) * len); char **str_keys = NULL; @@ -38858,8 +37133,6 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_STRING], JS_AtomToString(ctx, JS_ATOM_empty_string)); obj = JS_NewGlobalCConstructor(ctx, "String", js_string_constructor, 1, ctx->class_proto[JS_CLASS_STRING]); - JS_SetPropertyFunctionList(ctx, obj, js_string_funcs, - countof(js_string_funcs)); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_funcs, countof(js_string_proto_funcs)); @@ -39009,7 +37282,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) 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_DefinePropertyValueStr(ctx, ctx->global_obj, "arrfor", JS_NewCFunction(ctx, js_cell_array_for, "for", 4), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "find", @@ -39056,6 +37329,25 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_DefinePropertyValueStr(ctx, ctx->global_obj, "remainder", JS_NewCFunction(ctx, js_cell_number_remainder, "remainder", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "character", + JS_NewCFunction(ctx, js_cell_character, "character", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* modulo(dividend, divisor) - dividend - (divisor * floor(dividend / divisor)) */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "modulo", + JS_NewCFunction(ctx, js_cell_modulo, "modulo", 2), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* neg(number) - negate a number */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "neg", + JS_NewCFunction(ctx, js_cell_neg, "neg", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + /* normalize(text) - Unicode normalize */ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "normalize", + JS_NewCFunction(ctx, js_cell_normalize, "normalize", 1), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + /* reverse() - reverse an array */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "reverse", diff --git a/test.ce b/test.ce index 79e0e9a1..50dfba6f 100644 --- a/test.ce +++ b/test.ce @@ -84,7 +84,7 @@ function parse_args() { var lock = shop.load_lock() if (lock[name]) { target_pkg = name - } else if (name.startsWith('/') && is_valid_package(name)) { + } else if (starts_with(name, '/') && is_valid_package(name)) { target_pkg = name } else { // Try to resolve as dependency alias from current package @@ -115,7 +115,7 @@ function parse_args() { var test_path = args[0] // Normalize path - add tests/ prefix if not present and doesn't start with / - if (!test_path.startsWith('tests/') && !test_path.startsWith('/')) { + if (!starts_with(test_path, 'tests/') && !starts_with(test_path, '/')) { // Check if file exists as-is first if (!fd.is_file(test_path + '.cm') && !fd.is_file(test_path)) { // Try with tests/ prefix @@ -144,8 +144,8 @@ if (!parse_args()) { function ensure_dir(path) { if (fd.is_dir(path)) return true - var parts = path.split('/') - var current = path.startsWith('/') ? '/' : '' + var parts = array(path, '/') + var current = starts_with(path, '/') ? '/' : '' for (var i = 0; i < parts.length; i++) { if (parts[i] == '') continue current += parts[i] + '/' @@ -161,7 +161,7 @@ function get_pkg_dir(package_name) { if (!package_name) { return fd.realpath('.') } - if (package_name.startsWith('/')) { + if (starts_with(package_name, '/')) { return package_name } return shop.get_package_dir(package_name) @@ -179,16 +179,16 @@ function collect_actor_tests(package_name, specific_test) { for (var i = 0; i < files.length; i++) { var f = files[i] // Check if file is in tests/ folder and is a .ce actor - if (f.startsWith("tests/") && f.endsWith(".ce")) { + if (starts_with(f, "tests/") && ends_with(f, ".ce")) { // If specific test requested, filter if (specific_test) { - var test_name = f.substring(0, f.length - 3) // remove .ce + var test_name = text(f, 0, -3) // remove .ce var match_name = specific_test - if (!match_name.startsWith('tests/')) match_name = 'tests/' + match_name - if (!match_name.endsWith('.ce')) match_name = match_name + if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name + if (!ends_with(match_name, '.ce')) match_name = match_name // Match without extension var test_base = test_name - var match_base = match_name.endsWith('.ce') ? match_name.substring(0, match_name.length - 3) : match_name + var match_base = ends_with(match_name, '.ce') ? text(match_name, 0, -3) : match_name if (test_base != match_base) continue } @@ -204,7 +204,7 @@ function collect_actor_tests(package_name, specific_test) { // Spawn an actor test and track it function spawn_actor_test(test_info) { - var test_name = test_info.file.substring(6, test_info.file.length - 3) // remove "tests/" and ".ce" + var test_name = text(test_info.file, 6, -3) // remove "tests/" and ".ce" log.console(` [ACTOR] ${test_info.file}`) var entry = { @@ -218,7 +218,7 @@ function spawn_actor_test(test_info) { try { // Spawn the actor test - it should send back results - var actor_path = test_info.path.substring(0, test_info.path.length - 3) // remove .ce + var actor_path = text(test_info.path, 0, -3) // remove .ce entry.actor = $start(actor_path) pending_actor_tests.push(entry) } catch (e) { @@ -250,14 +250,14 @@ function run_tests(package_name, specific_test) { for (var i = 0; i < files.length; i++) { var f = files[i] // Check if file is in tests/ folder and is a .cm module (not .ce - those are actor tests) - if (f.startsWith("tests/") && f.endsWith(".cm")) { + if (starts_with(f, "tests/") && ends_with(f, ".cm")) { // If specific test requested, filter if (specific_test) { - var test_name = f.substring(0, f.length - 3) // remove .cm + var test_name = text(f, 0, -3) // remove .cm var match_name = specific_test - if (!match_name.startsWith('tests/')) match_name = 'tests/' + match_name + if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name // Match without extension - var match_base = match_name.endsWith('.cm') ? match_name.substring(0, match_name.length - 3) : match_name + var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name if (test_name != match_base) continue } test_files.push(f) @@ -271,7 +271,7 @@ function run_tests(package_name, specific_test) { for (var i = 0; i < test_files.length; i++) { var f = test_files[i] - var mod_path = f.substring(0, f.length - 3) // remove .cm + var mod_path = text(f, 0, -3) // remove .cm var file_result = { name: f, @@ -336,7 +336,7 @@ function run_tests(package_name, specific_test) { log.console(` FAIL ${t.name} ${test_entry.error.message}`) if (test_entry.error.stack) { - log.console(` ${text(test_entry.error.stack.split('\n'), '\n ')}`) + log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`) } pkg_result.failed++ @@ -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${text(t.error.stack.split('\n').map(function(l){return ` ${l}`}), '\n')}\n` + txt_report += ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n` } } txt_report += `\n` diff --git a/tests/comments.cm b/tests/comments.cm index c4465755..c618d52e 100644 --- a/tests/comments.cm +++ b/tests/comments.cm @@ -15,7 +15,7 @@ return { } $receiver(tree => { - var child_reqs = tree.children.map(child => cb => { + var child_reqs = array(tree.children, child => cb => { $start(e => send(e.actor, child, cb), "tests/comments") }) diff --git a/tests/comments_actor.ce b/tests/comments_actor.ce index 84c7750b..c7b70222 100644 --- a/tests/comments_actor.ce +++ b/tests/comments_actor.ce @@ -9,7 +9,7 @@ function load_comment_from_api_requestor(id) { } $receiver(tree => { - var child_reqs = tree.children.map(child => cb => { + var child_reqs = array(tree.children, child => cb => { $start(e => send(e.actor, child, cb), "tests/comments") // Note: recursively calls itself? Original used "tests/comments" // We should probably change this to "tests/comments_actor" if it's recursive }) diff --git a/tests/guid.cm b/tests/guid.cm index 0d9e89eb..757c8c53 100644 --- a/tests/guid.cm +++ b/tests/guid.cm @@ -11,7 +11,7 @@ return { guid = text(guid,'h') st = time.number()-st log.console(`took ${btime*1000000} us to make blob; took ${st*1000000} us to make it text`) - log.console(guid.toLowerCase()) + log.console(lower(guid)) log.console(guid.length) } } diff --git a/tests/nota.cm b/tests/nota.cm index 071bcb87..4d92e92d 100644 --- a/tests/nota.cm +++ b/tests/nota.cm @@ -65,8 +65,8 @@ function deepCompare(expected, actual, path) { } if (is_object(expected) && is_object(actual)) { - var expKeys = array(expected).sort(); - var actKeys = array(actual).sort(); + var expKeys = sort(array(expected)) + var actKeys = sort(array(actual)) if (JSON.stringify(expKeys) != JSON.stringify(actKeys)) return { passed: false, diff --git a/tests/suite.cm b/tests/suite.cm index 2324777e..bc91f30c 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -1013,44 +1013,6 @@ return { if (is_blob(function(){})) throw "is_blob function should be false" }, - // ============================================================================ - // ISA TYPE CHECKING - // ============================================================================ - - test_isa_number: function() { - if (!is_number(42)) throw "isa number failed" - if (is_number("42")) throw "isa string not number failed" - }, - - test_isa_text: function() { - if (!is_text("hello")) throw "isa text failed" - if (is_text(123)) throw "isa number not text failed" - }, - - test_isa_logical: function() { - if (!is_logical(true)) throw "isa true failed" - if (!is_logical(false)) throw "isa false failed" - if (is_logical(1)) throw "isa number not logical failed" - }, - - test_isa_array: function() { - if (!is_array([], array)) throw "isa empty array failed" - if (!is_array([1,2,3], array)) throw "isa array failed" - if (is_array({}, array)) throw "isa object not array failed" - }, - - test_isa_object: function() { - if (!is_object({}, object)) throw "isa empty object failed" - if (!is_object({a:1}, object)) throw "isa object failed" - if (is_object([], object)) throw "isa array not object failed" - if (is_object(null, object)) throw "isa null not object failed" - }, - - test_isa_null: function() { - if (is_null(null)) throw "null not number" - if (is_null(text)) throw "null not text" - if (is_null(object)) throw "null not object" - }, test_is_proto: function() { var a = {} @@ -1577,41 +1539,23 @@ return { if (str != "1,2,3") throw "array join with text() failed" }, - test_array_indexOf: function() { - var arr = [10, 20, 30, 20] - if (arr.indexOf(20) != 1) throw "array indexOf failed" - if (arr.indexOf(99) != -1) throw "array indexOf not found failed" - }, - - test_array_lastIndexOf: function() { - var arr = [10, 20, 30, 20] - if (arr.lastIndexOf(20) != 3) throw "array lastIndexOf failed" - if (arr.lastIndexOf(99) != -1) throw "array lastIndexOf not found failed" - }, - // ============================================================================ // STRING METHODS // ============================================================================ - test_string_charAt: function() { - var str = "hello" - if (str.charAt(0) != "h") throw "string charAt first failed" - if (str.charAt(4) != "o") throw "string charAt last failed" - }, - - test_string_charCodeAt: function() { - var str = "A" - if (str.charCodeAt(0) != 65) throw "string charCodeAt failed" - }, - test_string_substring: function() { var str = "hello" - if (str.substring(1, 4) != "ell") throw "string substring failed" + if (text(str, 1, 4) != "ell") throw "string substring failed" }, - test_string_substr: function() { + test_string_substring_first: function() { var str = "hello" - if (str.substr(1, 3) != "ell") throw "string substr failed" + if (text(str, 1) != "ello") throw "string substring first failed" + }, + + test_string_substring_to_neg: function() { + var str = "hello" + if (text(str, 1, -2) != "el") throw "string substring to negative failed" }, test_string_slice: function() { @@ -1633,22 +1577,17 @@ return { test_string_toLowerCase: function() { var str = "HELLO" - if (str.toLowerCase() != "hello") throw "string toLowerCase failed" + if (lower(str) != "hello") throw "string toLowerCase failed" }, test_string_toUpperCase: function() { var str = "hello" - if (str.toUpperCase() != "HELLO") throw "string toUpperCase failed" - }, - - test_string_trim: function() { - var str = " hello " - if (str.trim() != "hello") throw "string trim failed" + if (upper(str) != "HELLO") throw "string toUpperCase failed" }, test_string_split: function() { var str = "a,b,c" - var parts = str.split(",") + var parts = array(str, ",") if (length(parts) != 3) throw "string split length failed" if (parts[1] != "b") throw "string split values failed" }, @@ -1665,11 +1604,6 @@ return { if (!hasNumbers) throw "string match with regex failed" }, - test_string_plus_string_works: function() { - var x = "hello" + " world" - if (x != "hello world") throw "string + string should work" - }, - null_access: function() { var val = {} var nn = val.a @@ -1795,6 +1729,13 @@ return { if (a[1] != 1) throw "array should be able to use number as key" }, + test_array_for: function() { + var a = [1,2,3] + arrfor(a, (x,i) => { + if (x-1 != i) throw "array for failed" + }) + }, + test_array_string_key_throws: function() { var a = [] var caught = false @@ -2220,5 +2161,1375 @@ return { if (result != "key:42") throw "proxy with integer bracket key failed" }, -} + // ============================================================================ + // REDUCE FUNCTION + // ============================================================================ + test_reduce_sum: function() { + var arr = [1, 2, 3, 4, 5] + var result = reduce(arr, (a, b) => a + b) + if (result != 15) throw "reduce sum failed" + }, + + test_reduce_product: function() { + var arr = [1, 2, 3, 4, 5] + var result = reduce(arr, (a, b) => a * b) + if (result != 120) throw "reduce product failed" + }, + + test_reduce_with_initial: function() { + var arr = [1, 2, 3] + var result = reduce(arr, (a, b) => a + b, 10) + if (result != 16) throw "reduce with initial failed" + }, + + test_reduce_with_initial_zero: function() { + var arr = [1, 2, 3] + var result = reduce(arr, (a, b) => a + b, 0) + if (result != 6) throw "reduce with initial zero failed" + }, + + test_reduce_empty_array_no_initial: function() { + var arr = [] + var result = reduce(arr, (a, b) => a + b) + if (result != null) throw "reduce empty array without initial should return null" + }, + + test_reduce_empty_array_with_initial: function() { + var arr = [] + var result = reduce(arr, (a, b) => a + b, 42) + if (result != 42) throw "reduce empty array with initial should return initial" + }, + + test_reduce_single_element_no_initial: function() { + var arr = [42] + var result = reduce(arr, (a, b) => a + b) + if (result != 42) throw "reduce single element without initial failed" + }, + + test_reduce_single_element_with_initial: function() { + var arr = [5] + var result = reduce(arr, (a, b) => a + b, 10) + if (result != 15) throw "reduce single element with initial failed" + }, + + test_reduce_reverse: function() { + var arr = [1, 2, 3, 4] + var result = reduce(arr, (a, b) => a - b, 0, true) + if (result != -10) throw "reduce reverse failed: " + result + }, + + test_reduce_string_concat: function() { + var arr = ["a", "b", "c"] + var result = reduce(arr, (a, b) => a + b) + if (result != "abc") throw "reduce string concat failed" + }, + + test_reduce_to_object: function() { + var arr = ["a", "b", "c"] + var result = reduce(arr, (obj, val, i) => { + obj[val] = i + return obj + }, {}) + if (result.a != 0 || result.b != 1 || result.c != 2) throw "reduce to object failed" + }, + + // ============================================================================ + // SORT FUNCTION + // ============================================================================ + + test_sort_numbers: function() { + var arr = [3, 1, 4, 1, 5, 9, 2, 6] + var sorted = sort(arr) + if (sorted[0] != 1 || sorted[1] != 1 || sorted[2] != 2) throw "sort numbers failed" + if (sorted[7] != 9) throw "sort numbers last element failed" + }, + + test_sort_strings: function() { + var arr = ["banana", "apple", "cherry"] + var sorted = sort(arr) + if (sorted[0] != "apple") throw "sort strings failed" + if (sorted[2] != "cherry") throw "sort strings last failed" + }, + + test_sort_preserves_original: function() { + var arr = [3, 1, 2] + var sorted = sort(arr) + if (arr[0] != 3) throw "sort should not mutate original" + }, + + test_sort_empty_array: function() { + var arr = [] + var sorted = sort(arr) + if (length(sorted) != 0) throw "sort empty array failed" + }, + + test_sort_single_element: function() { + var arr = [42] + var sorted = sort(arr) + if (sorted[0] != 42) throw "sort single element failed" + }, + + test_sort_by_field: function() { + var arr = [ + {name: "Charlie", age: 30}, + {name: "Alice", age: 25}, + {name: "Bob", age: 35} + ] + var sorted = sort(arr, "name") + if (sorted[0].name != "Alice") throw "sort by field failed" + if (sorted[2].name != "Charlie") throw "sort by field last failed" + }, + + test_sort_by_index: function() { + var arr = [[3, "c"], [1, "a"], [2, "b"]] + var sorted = sort(arr, 0) + if (sorted[0][1] != "a") throw "sort by index failed" + }, + + test_sort_stable: function() { + var arr = [ + {name: "A", order: 1}, + {name: "B", order: 1}, + {name: "C", order: 1} + ] + var sorted = sort(arr, "order") + if (sorted[0].name != "A" || sorted[1].name != "B" || sorted[2].name != "C") { + throw "sort should be stable" + } + }, + + test_sort_negative_numbers: function() { + var arr = [-5, 3, -1, 0, 2] + var sorted = sort(arr) + if (sorted[0] != -5 || sorted[4] != 3) throw "sort negative numbers failed" + }, + + // ============================================================================ + // FILTER FUNCTION + // ============================================================================ + + test_filter_basic: function() { + var arr = [1, 2, 3, 4, 5, 6] + var evens = filter(arr, x => x % 2 == 0) + if (length(evens) != 3) throw "filter basic length failed" + if (evens[0] != 2 || evens[1] != 4 || evens[2] != 6) throw "filter basic values failed" + }, + + test_filter_all_pass: function() { + var arr = [2, 4, 6] + var result = filter(arr, x => x % 2 == 0) + if (length(result) != 3) throw "filter all pass failed" + }, + + test_filter_none_pass: function() { + var arr = [1, 3, 5] + var result = filter(arr, x => x % 2 == 0) + if (length(result) != 0) throw "filter none pass failed" + }, + + test_filter_empty_array: function() { + var arr = [] + var result = filter(arr, x => true) + if (length(result) != 0) throw "filter empty array failed" + }, + + test_filter_with_index: function() { + var arr = ["a", "b", "c", "d"] + var result = filter(arr, (x, i) => i % 2 == 0) + if (length(result) != 2) throw "filter with index length failed" + if (result[0] != "a" || result[1] != "c") throw "filter with index values failed" + }, + + test_filter_preserves_original: function() { + var arr = [1, 2, 3] + var result = filter(arr, x => x > 1) + if (length(arr) != 3) throw "filter should not mutate original" + }, + + test_filter_objects: function() { + var arr = [{active: true}, {active: false}, {active: true}] + var result = filter(arr, x => x.active) + if (length(result) != 2) throw "filter objects failed" + }, + + // ============================================================================ + // FIND FUNCTION + // ============================================================================ + + test_find_basic: function() { + var arr = [1, 2, 3, 4, 5] + var idx = find(arr, x => x > 3) + if (idx != 3) throw "find basic failed" + }, + + test_find_first_element: function() { + var arr = [10, 2, 3] + var idx = find(arr, x => x > 5) + if (idx != 0) throw "find first element failed" + }, + + test_find_last_element: function() { + var arr = [1, 2, 10] + var idx = find(arr, x => x > 5) + if (idx != 2) throw "find last element failed" + }, + + test_find_not_found: function() { + var arr = [1, 2, 3] + var idx = find(arr, x => x > 10) + if (idx != null) throw "find not found should return null" + }, + + test_find_empty_array: function() { + var arr = [] + var idx = find(arr, x => true) + if (idx != null) throw "find in empty array should return null" + }, + + test_find_by_value: function() { + var arr = [10, 20, 30, 20] + var idx = find(arr, 20) + if (idx != 1) throw "find by value failed" + }, + + test_find_reverse: function() { + var arr = [10, 20, 30, 20] + var idx = find(arr, 20, true) + if (idx != 3) throw "find reverse failed" + }, + + test_find_with_from: function() { + var arr = [10, 20, 30, 20] + var idx = find(arr, 20, false, 2) + if (idx != 3) throw "find with from failed" + }, + + test_find_with_index_callback: function() { + var arr = ["a", "b", "c"] + var idx = find(arr, (x, i) => i == 1) + if (idx != 1) throw "find with index callback failed" + }, + + // ============================================================================ + // ABS FUNCTION + // ============================================================================ + + test_abs_positive: function() { + if (abs(5) != 5) throw "abs positive failed" + }, + + test_abs_negative: function() { + if (abs(-5) != 5) throw "abs negative failed" + }, + + test_abs_zero: function() { + if (abs(0) != 0) throw "abs zero failed" + }, + + test_abs_float: function() { + if (abs(-3.14) != 3.14) throw "abs float failed" + }, + + test_abs_non_number: function() { + if (abs("5") != null) throw "abs non-number should return null" + if (abs(null) != null) throw "abs null should return null" + }, + + // ============================================================================ + // FLOOR FUNCTION + // ============================================================================ + + test_floor_positive: function() { + if (floor(3.7) != 3) throw "floor positive failed" + }, + + test_floor_negative: function() { + if (floor(-3.7) != -4) throw "floor negative failed" + }, + + test_floor_integer: function() { + if (floor(5) != 5) throw "floor integer failed" + }, + + test_floor_zero: function() { + if (floor(0) != 0) throw "floor zero failed" + }, + + test_floor_with_place: function() { + if (floor(12.3775, -2) != 12.37) throw "floor with place failed" + }, + + test_floor_negative_with_place: function() { + if (floor(-12.3775, -2) != -12.38) throw "floor negative with place failed" + }, + + // ============================================================================ + // CEILING FUNCTION + // ============================================================================ + + test_ceiling_positive: function() { + if (ceiling(3.2) != 4) throw "ceiling positive failed" + }, + + test_ceiling_negative: function() { + if (ceiling(-3.7) != -3) throw "ceiling negative failed" + }, + + test_ceiling_integer: function() { + if (ceiling(5) != 5) throw "ceiling integer failed" + }, + + test_ceiling_zero: function() { + if (ceiling(0) != 0) throw "ceiling zero failed" + }, + + test_ceiling_with_place: function() { + if (ceiling(12.3775, -2) != 12.38) throw "ceiling with place failed" + }, + + test_ceiling_negative_with_place: function() { + if (ceiling(-12.3775, -2) != -12.37) throw "ceiling negative with place failed" + }, + + // ============================================================================ + // ROUND FUNCTION + // ============================================================================ + + test_round_down: function() { + if (round(3.4) != 3) throw "round down failed" + }, + + test_round_up: function() { + if (round(3.6) != 4) throw "round up failed" + }, + + test_round_half: function() { + if (round(3.5) != 4) throw "round half failed" + }, + + test_round_negative: function() { + if (round(-3.5) != -3 && round(-3.5) != -4) throw "round negative failed" + }, + + test_round_integer: function() { + if (round(5) != 5) throw "round integer failed" + }, + + test_round_with_places: function() { + if (round(12.3775, -2) != 12.38) throw "round with places failed" + }, + + test_round_to_tens: function() { + if (round(12.3775, 1) != 10) throw "round to tens failed" + }, + + // ============================================================================ + // TRUNC FUNCTION + // ============================================================================ + + test_trunc_positive: function() { + if (trunc(3.7) != 3) throw "trunc positive failed" + }, + + test_trunc_negative: function() { + if (trunc(-3.7) != -3) throw "trunc negative failed" + }, + + test_trunc_integer: function() { + if (trunc(5) != 5) throw "trunc integer failed" + }, + + test_trunc_zero: function() { + if (trunc(0) != 0) throw "trunc zero failed" + }, + + test_trunc_with_places: function() { + if (trunc(12.3775, -2) != 12.37) throw "trunc with places failed" + }, + + test_trunc_negative_with_places: function() { + if (trunc(-12.3775, -2) != -12.37) throw "trunc negative with places failed" + }, + + // ============================================================================ + // SIGN FUNCTION + // ============================================================================ + + test_sign_positive: function() { + if (sign(5) != 1) throw "sign positive failed" + }, + + test_sign_negative: function() { + if (sign(-5) != -1) throw "sign negative failed" + }, + + test_sign_zero: function() { + if (sign(0) != 0) throw "sign zero failed" + }, + + test_sign_float: function() { + if (sign(0.001) != 1) throw "sign positive float failed" + if (sign(-0.001) != -1) throw "sign negative float failed" + }, + + test_sign_non_number: function() { + if (sign("5") != null) throw "sign non-number should return null" + }, + + // ============================================================================ + // WHOLE AND FRACTION FUNCTIONS + // ============================================================================ + + test_whole_positive: function() { + if (whole(3.7) != 3) throw "whole positive failed" + }, + + test_whole_negative: function() { + if (whole(-3.7) != -3) throw "whole negative failed" + }, + + test_whole_integer: function() { + if (whole(5) != 5) throw "whole integer failed" + }, + + test_whole_non_number: function() { + if (whole("5") != null) throw "whole non-number should return null" + }, + + test_fraction_positive: function() { + var f = fraction(3.75) + if (f < 0.74 || f > 0.76) throw "fraction positive failed: " + f + }, + + test_fraction_negative: function() { + var f = fraction(-3.75) + if (f > -0.74 || f < -0.76) throw "fraction negative failed: " + f + }, + + test_fraction_integer: function() { + if (fraction(5) != 0) throw "fraction integer failed" + }, + + test_fraction_non_number: function() { + if (fraction("5") != null) throw "fraction non-number should return null" + }, + + // ============================================================================ + // NEG FUNCTION + // ============================================================================ + + test_neg_positive: function() { + if (neg(5) != -5) throw "neg positive failed" + }, + + test_neg_negative: function() { + if (neg(-5) != 5) throw "neg negative failed" + }, + + test_neg_zero: function() { + if (neg(0) != 0) throw "neg zero failed" + }, + + test_neg_float: function() { + if (neg(3.14) != -3.14) throw "neg float failed" + }, + + test_neg_non_number: function() { + if (neg("5") != null) throw "neg non-number should return null" + }, + + // ============================================================================ + // MODULO FUNCTION + // ============================================================================ + + test_modulo_positive: function() { + if (modulo(10, 3) != 1) throw "modulo positive failed" + }, + + test_modulo_negative_dividend: function() { + var result = modulo(-10, 3) + if (result != 2) throw "modulo negative dividend failed: " + result + }, + + test_modulo_negative_divisor: function() { + var result = modulo(10, -3) + if (result != -2) throw "modulo negative divisor failed: " + result + }, + + test_modulo_both_negative: function() { + var result = modulo(-10, -3) + if (result != -1) throw "modulo both negative failed: " + result + }, + + test_modulo_zero_dividend: function() { + if (modulo(0, 5) != 0) throw "modulo zero dividend failed" + }, + + test_modulo_zero_divisor: function() { + if (modulo(10, 0) != null) throw "modulo zero divisor should return null" + }, + + test_modulo_floats: function() { + var result = modulo(5.5, 2) + if (result < 1.4 || result > 1.6) throw "modulo floats failed: " + result + }, + + // ============================================================================ + // MIN AND MAX FUNCTIONS + // ============================================================================ + + test_min_basic: function() { + if (min(3, 5) != 3) throw "min basic failed" + }, + + test_min_equal: function() { + if (min(5, 5) != 5) throw "min equal failed" + }, + + test_min_negative: function() { + if (min(-3, -5) != -5) throw "min negative failed" + }, + + test_min_mixed: function() { + if (min(-3, 5) != -3) throw "min mixed failed" + }, + + test_min_float: function() { + if (min(3.14, 2.71) != 2.71) throw "min float failed" + }, + + test_min_non_number: function() { + if (min(3, "5") != null) throw "min non-number should return null" + if (min("3", 5) != null) throw "min first non-number should return null" + }, + + test_max_basic: function() { + if (max(3, 5) != 5) throw "max basic failed" + }, + + test_max_equal: function() { + if (max(5, 5) != 5) throw "max equal failed" + }, + + test_max_negative: function() { + if (max(-3, -5) != -3) throw "max negative failed" + }, + + test_max_mixed: function() { + if (max(-3, 5) != 5) throw "max mixed failed" + }, + + test_max_float: function() { + if (max(3.14, 2.71) != 3.14) throw "max float failed" + }, + + test_max_non_number: function() { + if (max(3, "5") != null) throw "max non-number should return null" + }, + + test_min_max_constrain: function() { + var val = 8 + var constrained = min(max(val, 0), 10) + if (constrained != 8) throw "min max constrain in range failed" + constrained = min(max(-5, 0), 10) + if (constrained != 0) throw "min max constrain below failed" + constrained = min(max(15, 0), 10) + if (constrained != 10) throw "min max constrain above failed" + }, + + // ============================================================================ + // CODEPOINT FUNCTION + // ============================================================================ + + test_codepoint_letter: function() { + if (codepoint("A") != 65) throw "codepoint A failed" + if (codepoint("a") != 97) throw "codepoint a failed" + }, + + test_codepoint_digit: function() { + if (codepoint("0") != 48) throw "codepoint 0 failed" + }, + + test_codepoint_unicode: function() { + if (codepoint("\u00E9") != 233) throw "codepoint unicode failed" + }, + + test_codepoint_first_char: function() { + if (codepoint("ABC") != 65) throw "codepoint should return first char" + }, + + test_codepoint_empty: function() { + if (codepoint("") != null) throw "codepoint empty should return null" + }, + + test_codepoint_non_text: function() { + if (codepoint(65) != null) throw "codepoint non-text should return null" + }, + + // ============================================================================ + // CHARACTER FUNCTION + // ============================================================================ + + test_character_letter: function() { + if (character(65) != "A") throw "character 65 failed" + if (character(97) != "a") throw "character 97 failed" + }, + + test_character_digit: function() { + if (character(48) != "0") throw "character 48 failed" + }, + + test_character_unicode: function() { + if (character(233) != "\u00E9") throw "character unicode failed" + }, + + test_character_from_text: function() { + if (character("hello") != "h") throw "character from text failed" + }, + + test_character_invalid: function() { + if (character(-1) != "") throw "character negative should return empty" + }, + + // ============================================================================ + // SEARCH FUNCTION + // ============================================================================ + + test_search_found: function() { + if (search("hello world", "world") != 6) throw "search found failed" + }, + + test_search_not_found: function() { + if (search("hello world", "xyz") != null) throw "search not found should return null" + }, + + test_search_beginning: function() { + if (search("hello world", "hello") != 0) throw "search beginning failed" + }, + + test_search_single_char: function() { + if (search("hello", "l") != 2) throw "search single char failed" + }, + + test_search_with_from: function() { + if (search("hello hello", "hello", 1) != 6) throw "search with from failed" + }, + + test_search_empty_pattern: function() { + if (search("hello", "") != 0) throw "search empty pattern failed" + }, + + test_search_negative_from: function() { + var result = search("hello world", "world", -5) + if (result != 6) throw "search negative from failed: " + result + }, + + // ============================================================================ + // REPLACE FUNCTION + // ============================================================================ + + test_replace_basic: function() { + var result = replace("hello world", "world", "universe") + if (result != "hello universe") throw "replace basic failed" + }, + + test_replace_not_found: function() { + var result = replace("hello world", "xyz", "abc") + if (result != "hello world") throw "replace not found should return original" + }, + + test_replace_multiple: function() { + var result = replace("banana", "a", "o") + if (result != "bonono") throw "replace multiple failed: " + result + }, + + test_replace_with_limit: function() { + var result = replace("banana", "a", "o", 1) + if (result != "bonana") throw "replace with limit failed: " + result + }, + + test_replace_empty_target: function() { + var result = replace("abc", "", "-") + if (result != "-a-b-c-") throw "replace empty target failed: " + result + }, + + test_replace_to_empty: function() { + var result = replace("hello", "l", "") + if (result != "heo") throw "replace to empty failed" + }, + + test_replace_with_function: function() { + var result = replace("hello", "l", (match, pos) => `[${pos}]`) + if (result != "he[2][3]o") throw "replace with function failed: " + result + }, + + // ============================================================================ + // TEXT FUNCTION (Conversion and Slicing) + // ============================================================================ + + test_text_number_basic: function() { + if (text(123) != "123") throw "text number basic failed" + }, + + test_text_number_negative: function() { + if (text(-456) != "-456") throw "text number negative failed" + }, + + test_text_number_float: function() { + var result = text(3.14) + if (result.indexOf("3.14") != 0) throw "text number float failed" + }, + + test_text_array_join: function() { + var result = text([1, 2, 3], ",") + if (result != "1,2,3") throw "text array join failed" + }, + + test_text_array_join_empty_sep: function() { + var result = text(["a", "b", "c"], "") + if (result != "abc") throw "text array join empty sep failed" + }, + + test_text_slice_basic: function() { + if (text("hello", 1, 4) != "ell") throw "text slice basic failed" + }, + + test_text_slice_from_only: function() { + if (text("hello", 2) != "llo") throw "text slice from only failed" + }, + + test_text_slice_negative_from: function() { + if (text("hello", -2) != "lo") throw "text slice negative from failed" + }, + + test_text_slice_negative_to: function() { + if (text("hello", 0, -2) != "hel") throw "text slice negative to failed" + }, + + test_text_boolean: function() { + if (text(true) != "true") throw "text true failed" + if (text(false) != "false") throw "text false failed" + }, + + test_text_null: function() { + if (text(null) != "null") throw "text null failed" + }, + + // ============================================================================ + // NUMBER FUNCTION (Conversion) + // ============================================================================ + + test_number_from_string: function() { + if (number("123") != 123) throw "number from string failed" + }, + + test_number_from_negative_string: function() { + if (number("-456") != -456) throw "number from negative string failed" + }, + + test_number_from_float_string: function() { + if (number("3.14") != 3.14) throw "number from float string failed" + }, + + test_number_invalid_string: function() { + if (number("abc") != null) throw "number invalid string should return null" + }, + + test_number_from_boolean: function() { + if (number(true) != 1) throw "number from true failed" + if (number(false) != 0) throw "number from false failed" + }, + + test_number_from_number: function() { + if (number(42) != 42) throw "number from number failed" + }, + + test_number_with_radix: function() { + if (number("FF", 16) != 255) throw "number hex failed" + if (number("1010", 2) != 10) throw "number binary failed" + }, + + test_number_leading_zeros: function() { + if (number("007") != 7) throw "number leading zeros failed" + }, + + // ============================================================================ + // ARRAY FUNCTION (Creator and Slicing) + // ============================================================================ + + test_array_create_with_length: function() { + var arr = array(5) + if (length(arr) != 5) throw "array create length failed" + if (arr[0] != null) throw "array create should init to null" + }, + + test_array_create_with_initial: function() { + var arr = array(3, 42) + if (arr[0] != 42 || arr[1] != 42 || arr[2] != 42) throw "array create with initial failed" + }, + + test_array_create_with_function: function() { + var arr = array(3, i => i * 2) + if (arr[0] != 0 || arr[1] != 2 || arr[2] != 4) throw "array create with function failed" + }, + + test_array_copy: function() { + var orig = [1, 2, 3] + var copy = array(orig) + copy[0] = 99 + if (orig[0] != 1) throw "array copy should not affect original" + }, + + test_array_slice_basic: function() { + var arr = [1, 2, 3, 4, 5] + var sliced = array(arr, 1, 3) + if (length(sliced) != 2) throw "array slice length failed" + if (sliced[0] != 2 || sliced[1] != 3) throw "array slice values failed" + }, + + test_array_slice_negative: function() { + var arr = [1, 2, 3, 4, 5] + var sliced = array(arr, -3) + if (length(sliced) != 3) throw "array slice negative failed" + if (sliced[0] != 3) throw "array slice negative value failed" + }, + + test_array_from_object_keys: function() { + var obj = {a: 1, b: 2, c: 3} + var keys = array(obj) + if (length(keys) != 3) throw "array from object keys length failed" + }, + + test_array_from_text: function() { + var arr = array("abc") + if (length(arr) != 3) throw "array from text length failed" + if (arr[0] != "a" || arr[1] != "b" || arr[2] != "c") throw "array from text values failed" + }, + + test_array_split_text: function() { + var arr = array("a,b,c", ",") + if (length(arr) != 3) throw "array split text length failed" + if (arr[1] != "b") throw "array split text value failed" + }, + + // ============================================================================ + // TRIM FUNCTION + // ============================================================================ + + test_trim_spaces: function() { + if (trim(" hello ") != "hello") throw "trim spaces failed" + }, + + test_trim_tabs: function() { + if (trim("\thello\t") != "hello") throw "trim tabs failed" + }, + + test_trim_mixed: function() { + if (trim(" \t hello \n ") != "hello") throw "trim mixed failed" + }, + + test_trim_no_whitespace: function() { + if (trim("hello") != "hello") throw "trim no whitespace failed" + }, + + test_trim_empty: function() { + if (trim("") != "") throw "trim empty failed" + }, + + test_trim_all_whitespace: function() { + if (trim(" ") != "") throw "trim all whitespace failed" + }, + + // ============================================================================ + // LOWER AND UPPER FUNCTIONS + // ============================================================================ + + test_lower_basic: function() { + if (lower("HELLO") != "hello") throw "lower basic failed" + }, + + test_lower_mixed: function() { + if (lower("HeLLo WoRLD") != "hello world") throw "lower mixed failed" + }, + + test_lower_already_lower: function() { + if (lower("hello") != "hello") throw "lower already lower failed" + }, + + test_lower_with_numbers: function() { + if (lower("ABC123") != "abc123") throw "lower with numbers failed" + }, + + test_upper_basic: function() { + if (upper("hello") != "HELLO") throw "upper basic failed" + }, + + test_upper_mixed: function() { + if (upper("HeLLo WoRLD") != "HELLO WORLD") throw "upper mixed failed" + }, + + test_upper_already_upper: function() { + if (upper("HELLO") != "HELLO") throw "upper already upper failed" + }, + + test_upper_with_numbers: function() { + if (upper("abc123") != "ABC123") throw "upper with numbers failed" + }, + + // ============================================================================ + // APPLY FUNCTION + // ============================================================================ + + test_apply_basic: function() { + var fn = function(a, b) { return a + b } + var result = apply(fn, [3, 4]) + if (result != 7) throw "apply basic failed" + }, + + test_apply_no_args: function() { + var fn = function() { return 42 } + var result = apply(fn, []) + if (result != 42) throw "apply no args failed" + }, + + test_apply_single_arg: function() { + var fn = function(x) { return x * 2 } + var result = apply(fn, [5]) + if (result != 10) throw "apply single arg failed" + }, + + test_apply_many_args: function() { + var fn = function(a, b, c, d) { return a + b + c + d } + var result = apply(fn, [1, 2, 3, 4]) + if (result != 10) throw "apply many args failed" + }, + + test_apply_non_function: function() { + var result = apply(42, [1, 2]) + if (result != 42) throw "apply non-function should return first arg" + }, + + // ============================================================================ + // CALL FUNCTION (Additional Tests) + // ============================================================================ + + test_call_many_args: function() { + var fn = function(a, b, c, d) { return a * b + c * d } + var result = call(fn, null, 2, 3, 4, 5) + if (result != 26) throw "call many args failed" + }, + + test_call_method_style: function() { + var obj = { + value: 10, + multiply: function(x) { return this.value * x } + } + var result = call(obj.multiply, obj, 5) + if (result != 50) throw "call method style failed" + }, + + test_call_change_this: function() { + var obj1 = { value: 10 } + var obj2 = { value: 20 } + var fn = function() { return this.value } + if (call(fn, obj1) != 10) throw "call this obj1 failed" + if (call(fn, obj2) != 20) throw "call this obj2 failed" + }, + + // ============================================================================ + // ARRFOR FUNCTION (Array For-Each) + // ============================================================================ + + test_arrfor_basic: function() { + var arr = [1, 2, 3] + var sum = 0 + arrfor(arr, x => { sum = sum + x }) + if (sum != 6) throw "arrfor basic failed" + }, + + test_arrfor_with_index: function() { + var arr = ["a", "b", "c"] + var indices = [] + arrfor(arr, (x, i) => { indices.push(i) }) + if (indices[0] != 0 || indices[2] != 2) throw "arrfor with index failed" + }, + + test_arrfor_empty: function() { + var called = false + arrfor([], x => { called = true }) + if (called) throw "arrfor empty should not call function" + }, + + test_arrfor_mutation: function() { + var arr = [1, 2, 3] + var results = [] + arrfor(arr, x => { results.push(x * 2) }) + if (results[0] != 2 || results[1] != 4 || results[2] != 6) throw "arrfor mutation failed" + }, + + // ============================================================================ + // STONE FUNCTION (Additional Tests) + // ============================================================================ + + test_stone_nested_object: function() { + var obj = {inner: {value: 42}} + stone(obj) + var caught = false + try { + obj.inner.value = 99 + } catch (e) { + caught = true + } + if (!caught) throw "stone should freeze nested objects" + }, + + test_stone_nested_array: function() { + var obj = {arr: [1, 2, 3]} + stone(obj) + var caught = false + try { + obj.arr[0] = 99 + } catch (e) { + caught = true + } + if (!caught) throw "stone should freeze nested arrays" + }, + + test_stone_returns_value: function() { + var obj = {x: 1} + var result = stone(obj) + if (result != obj) throw "stone should return the value" + }, + + test_stone_idempotent: function() { + var obj = {x: 1} + stone(obj) + stone(obj) + if (!stone.p(obj)) throw "stone should be idempotent" + }, + + // ============================================================================ + // PROTO FUNCTION (Additional Tests) + // ============================================================================ + + test_proto_chain: function() { + var grandparent = {a: 1} + var parent = meme(grandparent) + var child = meme(parent) + if (proto(child) != parent) throw "proto chain child->parent failed" + if (proto(parent) != grandparent) throw "proto chain parent->grandparent failed" + }, + + test_proto_array: function() { + var arr = [1, 2, 3] + var p = proto(arr) + if (p == null) throw "proto of array should not be null" + }, + + // ============================================================================ + // MEME FUNCTION (Additional Tests) + // ============================================================================ + + test_meme_method_inheritance: function() { + var parent = { + greet: function() { return "hello" } + } + var child = meme(parent) + if (child.greet() != "hello") throw "meme method inheritance failed" + }, + + test_meme_this_in_inherited_method: function() { + var parent = { + getValue: function() { return this.value } + } + var child = meme(parent) + child.value = 42 + if (child.getValue() != 42) throw "meme this in inherited method failed" + }, + + test_meme_deep_chain: function() { + var a = {x: 1} + var b = meme(a) + var c = meme(b) + var d = meme(c) + if (d.x != 1) throw "meme deep chain failed" + }, + + // ============================================================================ + // DELETE OPERATOR + // ============================================================================ + + test_delete_property: function() { + var obj = {a: 1, b: 2} + delete obj.a + if ("a" in obj) throw "delete property failed" + if (obj.b != 2) throw "delete should not affect other properties" + }, + + test_delete_array_element: function() { + var arr = [1, 2, 3] + delete arr[1] + if (arr[1] != null) throw "delete array element should set to null" + if (length(arr) != 3) throw "delete array element should not change length" + }, + + test_delete_nonexistent: function() { + var obj = {a: 1} + delete obj.b + if (obj.a != 1) throw "delete nonexistent should not affect object" + }, + + // ============================================================================ + // TYPEOF-LIKE BEHAVIOR + // ============================================================================ + + test_is_integer: function() { + if (!is_number(5) || 5 % 1 != 0) throw "is_integer positive failed" + if (!is_number(-5) || -5 % 1 != 0) throw "is_integer negative failed" + if (is_number(5.5) && 5.5 % 1 == 0) throw "is_integer float should not be integer" + }, + + // ============================================================================ + // ARRAY MAP-LIKE WITH ARRAY FUNCTION + // ============================================================================ + + test_array_map_basic: function() { + var arr = [1, 2, 3] + var doubled = array(arr, x => x * 2) + if (doubled[0] != 2 || doubled[1] != 4 || doubled[2] != 6) throw "array map basic failed" + }, + + test_array_map_with_index: function() { + var arr = ["a", "b", "c"] + var result = array(arr, (x, i) => `${x}${i}`) + if (result[0] != "a0" || result[1] != "b1") throw "array map with index failed" + }, + + test_array_map_reverse: function() { + var arr = [1, 2, 3] + var result = array(arr, x => x * 2, true) + if (result[0] != 6 || result[2] != 2) throw "array map reverse failed" + }, + + test_array_map_with_exit: function() { + var arr = [1, 2, 3, 4, 5] + var result = array(arr, x => { + if (x > 3) return null + return x * 2 + }, false, null) + if (length(result) != 5) throw "array map with exit length unexpected" + }, + + // ============================================================================ + // ERROR OBJECTS + // ============================================================================ + + test_error_creation: function() { + var e = Error("test message") + if (e.message != "test message") throw "Error creation failed" + }, + + test_error_is_proto: function() { + var e = Error("test") + if (!is_proto(e, Error)) throw "Error is_proto failed" + }, + + test_throw_error_object: function() { + var caught = null + try { + throw Error("my error") + } catch (e) { + caught = e + } + if (!caught || caught.message != "my error") throw "throw Error object failed" + }, + + // ============================================================================ + // REGEX TESTS + // ============================================================================ + + test_regex_test: function() { + var re = /hello/ + if (!re.test("hello world")) throw "regex test match failed" + if (re.test("goodbye world")) throw "regex test no match failed" + }, + + test_regex_test_case_sensitive: function() { + var re = /hello/ + if (re.test("HELLO")) throw "regex should be case sensitive by default" + }, + + test_regex_test_case_insensitive: function() { + var re = /hello/i + if (!re.test("HELLO")) throw "regex case insensitive failed" + }, + + test_regex_digit: function() { + var re = /\d+/ + if (!re.test("abc123")) throw "regex digit failed" + if (re.test("abc")) throw "regex digit no match failed" + }, + + test_regex_word_boundary: function() { + var re = /\bword\b/ + if (!re.test("a word here")) throw "regex word boundary failed" + if (re.test("awordhere")) throw "regex word boundary no match failed" + }, + + // ============================================================================ + // STRING METHOD EDGE CASES + // ============================================================================ + + test_string_startsWith: function() { + if (!starts_with("hello", "hel")) throw "startsWith match failed" + if (starts_with("hello", "ell")) throw "startsWith no match failed" + if (!starts_with("hello", "")) throw "startsWith empty should match" + }, + + test_string_endsWith: function() { + if (!ends_with("hello", "llo")) throw "endsWith match failed" + if (ends_with("hello", "ell")) throw "endsWith no match failed" + if (!ends_with("hello", "")) throw "endsWith empty should match" + }, + + test_string_includes: function() { + if (search("hello world", "world") == null) throw "includes match failed" + if (search("hello", "xyz") != null) throw "includes no match failed" + if (search("hello", "") == null) throw "includes empty should match" + }, + + // ============================================================================ + // ARRAY METHOD EDGE CASES + // ============================================================================ + + test_array_includes: function() { + var arr = [1, 2, 3] + if (find(arr, 2) == null) throw "array includes match failed" + if (find(arr, 5) != null) throw "array includes no match failed" + }, + + test_array_every: function() { + var arr = [2, 4, 6] + if (!every(arr, x => x % 2 == 0)) throw "array every all pass failed" + arr = [2, 3, 6] + if (every(arr, x => x % 2 == 0)) throw "array every not all pass failed" + }, + + test_array_some: function() { + var arr = [1, 2, 3] + if (!some(arr, x => x > 2)) throw "array some match failed" + if (some(arr, x => x > 5)) throw "array some no match failed" + }, + + test_array_flat: function() { + var arr = [1, [2, 3], [4, [5]]] + var flat = arr.flat() + if (flat[0] != 1 || flat[1] != 2 || flat[2] != 3) throw "array flat failed" + }, + + // ============================================================================ + // LOGICAL FUNCTION + // ============================================================================ + + test_logical_zero: function() { + if (logical(0) != false) throw "logical(0) should be false" + }, + + test_logical_one: function() { + if (logical(1) != true) throw "logical(1) should be true" + }, + + test_logical_string_true: function() { + if (logical("true") != true) throw "logical('true') should be true" + }, + + test_logical_string_false: function() { + if (logical("false") != false) throw "logical('false') should be false" + }, + + test_logical_boolean_true: function() { + if (logical(true) != true) throw "logical(true) should be true" + }, + + test_logical_boolean_false: function() { + if (logical(false) != false) throw "logical(false) should be false" + }, + + test_logical_null: function() { + if (logical(null) != false) throw "logical(null) should be false" + }, + + test_logical_invalid: function() { + if (logical("invalid") != null) throw "logical('invalid') should be null" + if (logical(42) != null) throw "logical(42) should be null" + if (logical({}) != null) throw "logical({}) should be null" + }, + + // ============================================================================ + // ADDITIONAL EDGE CASES + // ============================================================================ + + test_nested_array_access: function() { + var arr = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] + if (arr[0][1][0] != 3) throw "nested array access failed" + if (arr[1][1][1] != 8) throw "nested array access deep failed" + }, + + test_nested_object_access: function() { + var obj = {a: {b: {c: {d: 42}}}} + if (obj.a.b.c.d != 42) throw "nested object access failed" + }, + + test_mixed_nested_access: function() { + var data = {users: [{name: "Alice"}, {name: "Bob"}]} + if (data.users[1].name != "Bob") throw "mixed nested access failed" + }, + + test_empty_object_iteration: function() { + var obj = {} + var count = 0 + for (var k in obj) { + count++ + } + if (count != 0) throw "empty object iteration failed" + }, + + test_empty_array_iteration: function() { + var arr = [] + var count = 0 + for (var i in arr) { + count++ + } + if (count != 0) throw "empty array iteration failed" + }, + + test_object_with_null_value: function() { + var obj = {a: null, b: 2} + if (obj.a != null) throw "object null value failed" + if (!("a" in obj)) throw "object with null should have key" + }, + + test_array_with_null_values: function() { + var arr = [1, null, 3] + if (arr[1] != null) throw "array null value failed" + if (length(arr) != 3) throw "array with null length failed" + }, + + test_function_returning_function: function() { + var outer = function(x) { + return function(y) { + return function(z) { + return x + y + z + } + } + } + if (outer(1)(2)(3) != 6) throw "function returning function failed" + }, + + test_immediately_invoked_function: function() { + var result = (function(x) { return x * 2 })(21) + if (result != 42) throw "immediately invoked function failed" + }, + +} diff --git a/tests/wota.cm b/tests/wota.cm index d64f68cc..76f2fae6 100644 --- a/tests/wota.cm +++ b/tests/wota.cm @@ -44,8 +44,8 @@ function deep_compare(expected, actual, path) { } if (is_object(expected) && is_object(actual)) { - var expKeys = array(expected).sort() - var actKeys = array(actual).sort() + var expKeys = sort(array(expected)) + var actKeys = sort(array(actual)) if (JSON.stringify(expKeys) != JSON.stringify(actKeys)) return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] } var msgs = [] diff --git a/time.cm b/time.cm index 11976d7b..a49f0b7b 100644 --- a/time.cm +++ b/time.cm @@ -161,7 +161,7 @@ function time_text(num = now(), dst = rec.dst; /* am/pm */ - if (fmt.includes("a")) { + if (search(fmt, "a") != null) { if (rec.hour >= 13) { rec.hour -= 12; fmt = fmt.replaceAll("a", "PM"); } else if (rec.hour == 12) { fmt = fmt.replaceAll("a", "PM"); } else if (rec.hour == 0) { rec.hour = 12; fmt = fmt.replaceAll("a", "AM"); } @@ -170,7 +170,7 @@ function time_text(num = now(), /* BCE/CE */ var year = rec.year > 0 ? rec.year : rec.year - 1; - if (fmt.includes("c")) { + if (search(fmt, "c") != null) { if (year < 0) { year = abs(year); fmt = fmt.replaceAll("c", "BC"); } else fmt = fmt.replaceAll("c", "AD"); } diff --git a/toml.cm b/toml.cm index afe6e805..c6bde1d2 100644 --- a/toml.cm +++ b/toml.cm @@ -27,7 +27,7 @@ function parse_toml(toml_text) { // Prefer Misty split if present; fall back to JS split. var lines = array(toml_text, '\n') - if (lines == null) lines = toml_text.split('\n') + if (lines == null) lines = array(toml_text, '\n') var result = {} var current_section = result @@ -37,11 +37,12 @@ function parse_toml(toml_text) { var line = trim(lines[i]) if (line == null) line = lines[i] // Skip empty lines and comments - if (!line || line.startsWith('#')) continue + if (!line || starts_with(line, '#')) continue // Section header - if (line.startsWith('[') && line.endsWith(']')) { - var section_path = parse_key_path(text(line, 1, -1)) + if (starts_with(line, '[') && ends_with(line, ']')) { + var inner = text(line, 1, -1) + var section_path = parse_key_path(inner) if (section_path == null) return null current_section = result @@ -66,18 +67,19 @@ function parse_toml(toml_text) { // Key-value pair var eq_index = line.indexOf('=') if (eq_index > 0) { - 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_part = trim(text(line, 0, eq_index)) + var value = trim(text(line, eq_index + 1)) + if (key_part == null) key_part = trim(text(line, 0, eq_index)) + if (value == null) value = trim(text(line, eq_index + 1)) var key = parse_key(key_part) if (key == null) return null - if (value.startsWith('"') && value.endsWith('"')) { - current_section[key] = toml_unescape(text(value, 1, -1)) + if (starts_with(value, '"') && ends_with(value, '"')) { + var unquoted = text(value, 1, -1) + current_section[key] = toml_unescape(unquoted) if (current_section[key] == null) return null - } else if (value.startsWith('[') && value.endsWith(']')) { + } else if (starts_with(value, '[') && ends_with(value, ']')) { current_section[key] = parse_array(value) if (current_section[key] == null) return null } else if (value == 'true' || value == 'false') { @@ -97,7 +99,7 @@ function parse_toml(toml_text) { function parse_key(str) { if (!is_text(str)) return null - if (str.startsWith('"') && str.endsWith('"')) { + if (starts_with(str, '"') && ends_with(str, '"')) { var inner = text(str, 1, -1) return toml_unescape(inner) } @@ -118,7 +120,7 @@ function parse_key_path(str) { in_quote = !in_quote } else if (c == '.' && !in_quote) { var piece = trim(current) - if (piece == null) piece = current.trim() + if (piece == null) piece = trim(current) parts.push(parse_key(piece)) current = '' continue @@ -127,7 +129,7 @@ function parse_key_path(str) { } var tail = trim(current) - if (tail == null) tail = current.trim() + if (tail == null) tail = trim(current) if (tail.length > 0) parts.push(parse_key(tail)) return parts @@ -139,7 +141,6 @@ function parse_array(str) { // 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 = [] @@ -154,7 +155,7 @@ function parse_array(str) { current += ch } else if (ch == ',' && !in_quotes) { var piece = trim(current) - if (piece == null) piece = current.trim() + if (piece == null) piece = trim(current) items.push(parse_value(piece)) current = '' } else { @@ -163,7 +164,7 @@ function parse_array(str) { } var last = trim(current) - if (last == null) last = current.trim() + if (last == null) last = trim(current) if (last) items.push(parse_value(last)) return items @@ -172,7 +173,7 @@ function parse_array(str) { function parse_value(str) { if (!is_text(str)) return null - if (str.startsWith('"') && str.endsWith('"')) { + if (starts_with(str, '"') && ends_with(str, '"')) { return toml_unescape(text(str, 1, -1)) } if (str == 'true' || str == 'false') return str == 'true' @@ -199,7 +200,7 @@ function encode_toml(obj) { } function quote_key(k) { - if (k.includes('.') || k.includes('"') || k.includes(' ')) { + if (search(k, '.') != null || search(k, '"') != null || search(k, ' ') != null) { return '"' + toml_escape(k) + '"' } return k diff --git a/update.ce b/update.ce index 6d41da21..b60c0557 100644 --- a/update.ce +++ b/update.ce @@ -42,10 +42,10 @@ for (var i = 0; i < args.length; i++) { } } else if (args[i] == '--follow-links') { follow_links = true - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { target_pkg = args[i] // Resolve relative paths to absolute paths - if (target_pkg == '.' || target_pkg.startsWith('./') || target_pkg.startsWith('../') || fd.is_dir(target_pkg)) { + if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { var resolved = fd.realpath(target_pkg) if (resolved) { target_pkg = resolved @@ -81,8 +81,8 @@ function update_and_fetch(pkg) if (new_entry) { if (new_entry.commit) { - var old_str = old_commit ? old_commit.substring(0, 8) : "(new)" - log.console(" " + effective_pkg + " " + old_str + " -> " + new_entry.commit.substring(0, 8)) + var old_str = old_commit ? text(old_commit, 0, 8) : "(new)" + log.console(" " + effective_pkg + " " + old_str + " -> " + text(new_entry.commit, 0, 8)) shop.fetch(effective_pkg) } else { // Local package - just ensure symlink is correct diff --git a/verify.ce b/verify.ce index b93dced2..2c244149 100644 --- a/verify.ce +++ b/verify.ce @@ -45,7 +45,7 @@ for (var i = 0; i < args.length; i++) { log.console(" --deep Traverse full dependency closure") log.console(" --target Verify builds for specific target") $stop() - } else if (!args[i].startsWith('-')) { + } else if (!starts_with(args[i], '-')) { scope = args[i] } } @@ -103,7 +103,7 @@ function verify_package(locator) { // For linked packages, verify link target if (link_target) { - if (link_target.startsWith('/')) { + if (starts_with(link_target, '/')) { // Local path target if (!fd.is_dir(link_target)) { add_error(locator + ": link target does not exist: " + link_target) @@ -121,7 +121,7 @@ function verify_package(locator) { // Check symlink is correct if (fd.is_link(pkg_dir)) { var current_target = fd.readlink(pkg_dir) - var expected_target = link_target.startsWith('/') ? link_target : shop.get_package_dir(link_target) + var expected_target = starts_with(link_target, '/') ? link_target : shop.get_package_dir(link_target) if (current_target != expected_target) { add_warning(locator + ": symlink target mismatch (expected " + expected_target + ", got " + current_target + ")") } @@ -180,7 +180,7 @@ function check_dangling_links() { for (var origin in links) { var target = links[origin] - if (target.startsWith('/')) { + if (starts_with(target, '/')) { if (!fd.is_dir(target)) { add_error("Dangling link: " + origin + " -> " + target + " (target does not exist)") } @@ -202,7 +202,7 @@ if (scope == 'shop') { var locator = scope // Resolve local paths - if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) { + if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { var resolved = fd.realpath(locator) if (resolved) { locator = resolved diff --git a/why.ce b/why.ce index 539cbec6..809bb970 100644 --- a/why.ce +++ b/why.ce @@ -11,7 +11,7 @@ var target = args[0] log.console("Searching for '" + target + "'...") var target_clean = target -if (target_clean.startsWith('/')) target_clean = target_clean.substring(1) +if (starts_with(target_clean, '/')) target_clean = text(target_clean, 1) var found = false @@ -23,9 +23,7 @@ function search(current_pkg, stack) { var deps = pkg.dependencies(current_pkg) // Sort for consistent output - var aliases = [] - for (var k in deps) aliases.push(k) - aliases.sort() + var aliases = sort(array(deps)) for (var i = 0; i < aliases.length; i++) { var alias = aliases[i] @@ -36,7 +34,7 @@ function search(current_pkg, stack) { var canon = parsed.path var locator_clean = locator - if (locator.includes('@')) locator_clean = locator.split('@')[0] + if (search(locator, '@') != null) locator_clean = array(locator, '@')[0] // Check if match // 1. Alias matches