@tishlang/tish-format 1.0.12 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
//! Array builtin methods.
|
|
2
|
+
|
|
3
|
+
use crate::helpers::normalize_index;
|
|
4
|
+
use tishlang_core::Value;
|
|
5
|
+
use tishlang_core::VmRef;
|
|
6
|
+
|
|
7
|
+
/// Create a new array Value from a Vec of Values.
|
|
8
|
+
pub fn from_vec(v: Vec<Value>) -> Value {
|
|
9
|
+
Value::Array(VmRef::new(v))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// Get the length of an array.
|
|
13
|
+
pub fn len(arr: &Value) -> Option<usize> {
|
|
14
|
+
match arr {
|
|
15
|
+
Value::Array(a) => Some(a.borrow().len()),
|
|
16
|
+
Value::NumberArray(a) => Some(a.borrow().len()),
|
|
17
|
+
_ => None,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Normalise `NumberArray → Array` so callers that don't have a packed fast path
|
|
22
|
+
/// can use this deopt helper rather than changing every `if let Value::Array` branch.
|
|
23
|
+
/// Returns the original value unchanged for anything that isn't a `NumberArray`.
|
|
24
|
+
#[inline]
|
|
25
|
+
fn as_boxed_array(arr: &Value) -> std::borrow::Cow<'_, Value> {
|
|
26
|
+
match arr {
|
|
27
|
+
Value::NumberArray(na) => std::borrow::Cow::Owned(Value::materialize_number_array(na)),
|
|
28
|
+
other => std::borrow::Cow::Borrowed(other),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Packed-HOF fast-path gate: when `arr` is a packed [`Value::NumberArray`] and `callback` is
|
|
33
|
+
/// callable, snapshot the `Vec<f64>` so a higher-order method can fold/scan it WITHOUT first
|
|
34
|
+
/// materialising a boxed `Vec<Value>` (the `as_boxed_array` deopt that otherwise allocates a
|
|
35
|
+
/// 24-byte-per-element copy on every call). Snapshotting — rather than holding the `VmRef` borrow
|
|
36
|
+
/// across the callback — matches the boxed path's copy semantics (mutations to the array from inside
|
|
37
|
+
/// the callback aren't observed mid-scan) and can't deadlock if the callback re-enters the same
|
|
38
|
+
/// array. Returns `None` to fall through to the generic boxed path (regular arrays, or a non-callable
|
|
39
|
+
/// second argument).
|
|
40
|
+
#[inline]
|
|
41
|
+
fn packed_snapshot<'c>(
|
|
42
|
+
arr: &Value,
|
|
43
|
+
callback: &'c Value,
|
|
44
|
+
) -> Option<(Vec<f64>, &'c tishlang_core::NativeFn)> {
|
|
45
|
+
match (arr, callback) {
|
|
46
|
+
(Value::NumberArray(na), Value::Function(cb)) => Some((na.borrow().clone(), cb)),
|
|
47
|
+
_ => None,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// A `Vec<f64>` HOF result → packed [`Value::NumberArray`] so a packed input keeps producing packed
|
|
52
|
+
/// output (memory stays 3× denser and downstream packed fast paths keep firing). Empty results stay
|
|
53
|
+
/// a boxed `Value::Array`, matching the convention that empty arrays are general-purpose containers
|
|
54
|
+
/// whose element type can't be inferred. Only reached from a `NumberArray` input, which already
|
|
55
|
+
/// implies packed arrays are enabled, so no extra flag check is needed.
|
|
56
|
+
#[inline]
|
|
57
|
+
fn packed_or_empty(nums: Vec<f64>) -> Value {
|
|
58
|
+
if nums.is_empty() {
|
|
59
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
60
|
+
} else {
|
|
61
|
+
Value::number_array(nums)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pub fn push(arr: &Value, args: &[Value]) -> Value {
|
|
66
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
67
|
+
if let Value::Array(arr) = arr {
|
|
68
|
+
let mut arr_mut = arr.borrow_mut();
|
|
69
|
+
for v in args {
|
|
70
|
+
arr_mut.push(v.clone());
|
|
71
|
+
}
|
|
72
|
+
Value::Number(arr_mut.len() as f64)
|
|
73
|
+
} else {
|
|
74
|
+
Value::Null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub fn pop(arr: &Value) -> Value {
|
|
79
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
80
|
+
if let Value::Array(arr) = arr {
|
|
81
|
+
arr.borrow_mut().pop().unwrap_or(Value::Null)
|
|
82
|
+
} else {
|
|
83
|
+
Value::Null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub fn shift(arr: &Value) -> Value {
|
|
88
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
89
|
+
if let Value::Array(arr) = arr {
|
|
90
|
+
let mut arr_mut = arr.borrow_mut();
|
|
91
|
+
if arr_mut.is_empty() {
|
|
92
|
+
Value::Null
|
|
93
|
+
} else {
|
|
94
|
+
arr_mut.remove(0)
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
Value::Null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub fn unshift(arr: &Value, args: &[Value]) -> Value {
|
|
102
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
103
|
+
if let Value::Array(arr) = arr {
|
|
104
|
+
let mut arr_mut = arr.borrow_mut();
|
|
105
|
+
for (i, v) in args.iter().enumerate() {
|
|
106
|
+
arr_mut.insert(i, v.clone());
|
|
107
|
+
}
|
|
108
|
+
Value::Number(arr_mut.len() as f64)
|
|
109
|
+
} else {
|
|
110
|
+
Value::Null
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub fn index_of(arr: &Value, search: &Value) -> Value {
|
|
115
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
116
|
+
if let Value::Array(arr) = arr {
|
|
117
|
+
let arr_borrow = arr.borrow();
|
|
118
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
119
|
+
if v.strict_eq(search) {
|
|
120
|
+
return Value::Number(i as f64);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
Value::Number(-1.0)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pub fn includes(arr: &Value, search: &Value, from: Option<&Value>) -> Value {
|
|
128
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
129
|
+
if let Value::Array(arr) = arr {
|
|
130
|
+
let arr_borrow = arr.borrow();
|
|
131
|
+
let len = arr_borrow.len() as i64;
|
|
132
|
+
let start = match from {
|
|
133
|
+
Some(Value::Number(n)) if *n >= 0.0 => (*n as i64).min(len).max(0) as usize,
|
|
134
|
+
Some(Value::Number(n)) if *n < 0.0 => ((len + *n as i64).max(0)) as usize,
|
|
135
|
+
_ => 0,
|
|
136
|
+
};
|
|
137
|
+
for v in arr_borrow.iter().skip(start) {
|
|
138
|
+
if v.strict_eq(search) {
|
|
139
|
+
return Value::Bool(true);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
Value::Bool(false)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
pub fn join(arr: &Value, sep: &Value) -> Value {
|
|
147
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
148
|
+
if let Value::Array(arr) = arr {
|
|
149
|
+
let separator = match sep {
|
|
150
|
+
Value::String(s) => s.to_string(),
|
|
151
|
+
_ => ",".to_string(),
|
|
152
|
+
};
|
|
153
|
+
let arr_borrow = arr.borrow();
|
|
154
|
+
// JS `Array.prototype.join`: null/undefined → "", everything else via JS ToString
|
|
155
|
+
// (nested arrays recurse to a comma-join, objects → "[object Object]").
|
|
156
|
+
let parts: Vec<String> = arr_borrow
|
|
157
|
+
.iter()
|
|
158
|
+
.map(|v| match v {
|
|
159
|
+
Value::Null => String::new(),
|
|
160
|
+
other => other.to_js_string(),
|
|
161
|
+
})
|
|
162
|
+
.collect();
|
|
163
|
+
Value::String(parts.join(&separator).into())
|
|
164
|
+
} else {
|
|
165
|
+
Value::Null
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub fn reverse(arr: &Value) -> Value {
|
|
170
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
171
|
+
if let Value::Array(arr) = arr {
|
|
172
|
+
arr.borrow_mut().reverse();
|
|
173
|
+
Value::Array(arr.clone())
|
|
174
|
+
} else {
|
|
175
|
+
Value::Null
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Fisher-Yates shuffle. Returns a new shuffled array (does not mutate).
|
|
180
|
+
pub fn shuffle(arr: &Value) -> Value {
|
|
181
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
182
|
+
if let Value::Array(arr) = arr {
|
|
183
|
+
let mut v = arr.borrow().clone();
|
|
184
|
+
use rand::seq::SliceRandom;
|
|
185
|
+
v.shuffle(&mut rand::rng());
|
|
186
|
+
Value::Array(VmRef::new(v))
|
|
187
|
+
} else {
|
|
188
|
+
Value::Null
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pub fn splice(arr: &Value, start: &Value, delete_count: Option<&Value>, items: &[Value]) -> Value {
|
|
193
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
194
|
+
if let Value::Array(arr) = arr {
|
|
195
|
+
let mut arr_mut = arr.borrow_mut();
|
|
196
|
+
let len = arr_mut.len() as i64;
|
|
197
|
+
let start_idx = normalize_index(start, len, 0);
|
|
198
|
+
let del_count = match delete_count {
|
|
199
|
+
Some(Value::Number(n)) => (*n as i64).max(0) as usize,
|
|
200
|
+
_ => (len as usize).saturating_sub(start_idx),
|
|
201
|
+
};
|
|
202
|
+
let actual_delete = del_count.min(arr_mut.len().saturating_sub(start_idx));
|
|
203
|
+
let removed: Vec<Value> = arr_mut
|
|
204
|
+
.splice(start_idx..start_idx + actual_delete, items.iter().cloned())
|
|
205
|
+
.collect();
|
|
206
|
+
Value::Array(VmRef::new(removed))
|
|
207
|
+
} else {
|
|
208
|
+
Value::Null
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// `Array.prototype.fill(value, start?, end?)` — overwrites elements in `[start, end)` with
|
|
213
|
+
/// `value`, mutating in place and returning the same array. start/end use JS index
|
|
214
|
+
/// normalization (negatives count from the end; defaults 0 and length). Issue #76.
|
|
215
|
+
pub fn fill(arr: &Value, value: &Value, start: &Value, end: &Value) -> Value {
|
|
216
|
+
let arr = as_boxed_array(arr);
|
|
217
|
+
let arr = &*arr;
|
|
218
|
+
if let Value::Array(arr) = arr {
|
|
219
|
+
let mut arr_mut = arr.borrow_mut();
|
|
220
|
+
let len = arr_mut.len() as i64;
|
|
221
|
+
let start_idx = normalize_index(start, len, 0);
|
|
222
|
+
let end_idx = normalize_index(end, len, len as usize);
|
|
223
|
+
let mut i = start_idx;
|
|
224
|
+
while i < end_idx && i < arr_mut.len() {
|
|
225
|
+
arr_mut[i] = value.clone();
|
|
226
|
+
i += 1;
|
|
227
|
+
}
|
|
228
|
+
Value::Array(arr.clone())
|
|
229
|
+
} else {
|
|
230
|
+
Value::Null
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
pub fn slice(arr: &Value, start: &Value, end: &Value) -> Value {
|
|
235
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
236
|
+
if let Value::Array(arr) = arr {
|
|
237
|
+
let arr_borrow = arr.borrow();
|
|
238
|
+
let len = arr_borrow.len() as i64;
|
|
239
|
+
let start_idx = normalize_index(start, len, 0);
|
|
240
|
+
let end_idx = normalize_index(end, len, len as usize);
|
|
241
|
+
let sliced = if start_idx < end_idx {
|
|
242
|
+
arr_borrow[start_idx..end_idx].to_vec()
|
|
243
|
+
} else {
|
|
244
|
+
vec![]
|
|
245
|
+
};
|
|
246
|
+
Value::Array(VmRef::new(sliced))
|
|
247
|
+
} else {
|
|
248
|
+
Value::Null
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
pub fn concat(arr: &Value, args: &[Value]) -> Value {
|
|
253
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
254
|
+
if let Value::Array(arr) = arr {
|
|
255
|
+
let mut result = arr.borrow().clone();
|
|
256
|
+
for v in args {
|
|
257
|
+
if let Value::Array(other) = v {
|
|
258
|
+
result.extend(other.borrow().iter().cloned());
|
|
259
|
+
} else {
|
|
260
|
+
result.push(v.clone());
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
Value::Array(VmRef::new(result))
|
|
264
|
+
} else {
|
|
265
|
+
Value::Null
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
pub fn flat(arr: &Value, depth: &Value) -> Value {
|
|
270
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
271
|
+
fn flatten(arr: &[Value], depth: i32, result: &mut Vec<Value>) {
|
|
272
|
+
for v in arr {
|
|
273
|
+
if depth > 0 {
|
|
274
|
+
if let Value::Array(inner) = v {
|
|
275
|
+
flatten(&inner.borrow(), depth - 1, result);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
result.push(v.clone());
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if let Value::Array(arr) = arr {
|
|
284
|
+
let d = match depth {
|
|
285
|
+
Value::Number(n) => *n as i32,
|
|
286
|
+
_ => 1,
|
|
287
|
+
};
|
|
288
|
+
let mut result = Vec::new();
|
|
289
|
+
flatten(&arr.borrow(), d, &mut result);
|
|
290
|
+
Value::Array(VmRef::new(result))
|
|
291
|
+
} else {
|
|
292
|
+
Value::Null
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Higher-order array methods require a callback function.
|
|
297
|
+
// These take NativeFn from tishlang_core::Value::Function
|
|
298
|
+
|
|
299
|
+
pub fn map(arr: &Value, callback: &Value) -> Value {
|
|
300
|
+
// Packed fast path: scan the `Vec<f64>` snapshot directly. Speculatively build a packed
|
|
301
|
+
// `Vec<f64>` so a numeric map (the common `x => x * k` case) keeps its result packed with NO
|
|
302
|
+
// boxed `Vec<Value>` intermediate — downstream packed fast paths then keep firing. Deopt to a
|
|
303
|
+
// boxed array on the FIRST non-numeric callback result; every element's callback still runs
|
|
304
|
+
// exactly once, in index order (the deopt resumes at `i + 1`).
|
|
305
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
306
|
+
let mut nums: Vec<f64> = Vec::with_capacity(data.len());
|
|
307
|
+
for (i, &n) in data.iter().enumerate() {
|
|
308
|
+
match cb.call(&[Value::Number(n), Value::Number(i as f64)]) {
|
|
309
|
+
Value::Number(r) => nums.push(r),
|
|
310
|
+
other => {
|
|
311
|
+
let mut boxed: Vec<Value> = nums.into_iter().map(Value::Number).collect();
|
|
312
|
+
boxed.push(other);
|
|
313
|
+
for (j, &m) in data.iter().enumerate().skip(i + 1) {
|
|
314
|
+
boxed.push(cb.call(&[Value::Number(m), Value::Number(j as f64)]));
|
|
315
|
+
}
|
|
316
|
+
return Value::Array(VmRef::new(boxed));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return packed_or_empty(nums);
|
|
321
|
+
}
|
|
322
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
323
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
324
|
+
let arr_borrow = arr.borrow();
|
|
325
|
+
let result: Vec<Value> = arr_borrow
|
|
326
|
+
.iter()
|
|
327
|
+
.enumerate()
|
|
328
|
+
.map(|(i, v)| cb.call(&[v.clone(), Value::Number(i as f64)]))
|
|
329
|
+
.collect();
|
|
330
|
+
Value::Array(VmRef::new(result))
|
|
331
|
+
} else {
|
|
332
|
+
Value::Null
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
pub fn filter(arr: &Value, callback: &Value) -> Value {
|
|
337
|
+
// Packed fast path: `filter` keeps a SUBSET of the input f64s, so the result is always numeric —
|
|
338
|
+
// build the packed `Vec<f64>` directly, no boxed intermediate, and hand back a `NumberArray`.
|
|
339
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
340
|
+
let mut out: Vec<f64> = Vec::new();
|
|
341
|
+
for (i, &n) in data.iter().enumerate() {
|
|
342
|
+
if cb.call(&[Value::Number(n), Value::Number(i as f64)]).is_truthy() {
|
|
343
|
+
out.push(n);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return packed_or_empty(out);
|
|
347
|
+
}
|
|
348
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
349
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
350
|
+
let arr_borrow = arr.borrow();
|
|
351
|
+
let result: Vec<Value> = arr_borrow
|
|
352
|
+
.iter()
|
|
353
|
+
.enumerate()
|
|
354
|
+
.filter_map(|(i, v)| {
|
|
355
|
+
let keep = cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
356
|
+
if keep.is_truthy() {
|
|
357
|
+
Some(v.clone())
|
|
358
|
+
} else {
|
|
359
|
+
None
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
.collect();
|
|
363
|
+
Value::Array(VmRef::new(result))
|
|
364
|
+
} else {
|
|
365
|
+
Value::Null
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
pub fn reduce(arr: &Value, callback: &Value, initial: &Value) -> Value {
|
|
370
|
+
// Packed fast path: fold the `Vec<f64>` snapshot directly. Same no-initial rule as the boxed
|
|
371
|
+
// path (absent init → first element as the seed, scan from index 1).
|
|
372
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
373
|
+
let (start_idx, mut acc) = if matches!(initial, Value::Null) && !data.is_empty() {
|
|
374
|
+
(1usize, Value::Number(data[0]))
|
|
375
|
+
} else {
|
|
376
|
+
(0usize, initial.clone())
|
|
377
|
+
};
|
|
378
|
+
// `skip(start_idx)` preserves the true element index for the callback's 3rd arg.
|
|
379
|
+
for (i, &x) in data.iter().enumerate().skip(start_idx) {
|
|
380
|
+
acc = cb.call(&[acc, Value::Number(x), Value::Number(i as f64)]);
|
|
381
|
+
}
|
|
382
|
+
return acc;
|
|
383
|
+
}
|
|
384
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
385
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
386
|
+
let arr_borrow = arr.borrow();
|
|
387
|
+
let len = arr_borrow.len();
|
|
388
|
+
let (start_idx, mut acc) = if matches!(initial, Value::Null) && !arr_borrow.is_empty() {
|
|
389
|
+
// No initial value: use first element as acc, start from index 1
|
|
390
|
+
(1, arr_borrow[0].clone())
|
|
391
|
+
} else {
|
|
392
|
+
(0, initial.clone())
|
|
393
|
+
};
|
|
394
|
+
for i in start_idx..len {
|
|
395
|
+
let v = arr_borrow[i].clone();
|
|
396
|
+
acc = cb.call(&[acc, v.clone(), Value::Number(i as f64)]);
|
|
397
|
+
}
|
|
398
|
+
acc
|
|
399
|
+
} else {
|
|
400
|
+
Value::Null
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
pub fn for_each(arr: &Value, callback: &Value) -> Value {
|
|
405
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
406
|
+
for (i, &n) in data.iter().enumerate() {
|
|
407
|
+
cb.call(&[Value::Number(n), Value::Number(i as f64)]);
|
|
408
|
+
}
|
|
409
|
+
return Value::Null;
|
|
410
|
+
}
|
|
411
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
412
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
413
|
+
let arr_borrow = arr.borrow();
|
|
414
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
415
|
+
cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
Value::Null
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
pub fn find(arr: &Value, callback: &Value) -> Value {
|
|
422
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
423
|
+
for (i, &n) in data.iter().enumerate() {
|
|
424
|
+
if cb.call(&[Value::Number(n), Value::Number(i as f64)]).is_truthy() {
|
|
425
|
+
return Value::Number(n);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return Value::Null;
|
|
429
|
+
}
|
|
430
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
431
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
432
|
+
let arr_borrow = arr.borrow();
|
|
433
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
434
|
+
let result = cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
435
|
+
if result.is_truthy() {
|
|
436
|
+
return v.clone();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
Value::Null
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
pub fn find_index(arr: &Value, callback: &Value) -> Value {
|
|
444
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
445
|
+
for (i, &n) in data.iter().enumerate() {
|
|
446
|
+
if cb.call(&[Value::Number(n), Value::Number(i as f64)]).is_truthy() {
|
|
447
|
+
return Value::Number(i as f64);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return Value::Number(-1.0);
|
|
451
|
+
}
|
|
452
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
453
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
454
|
+
let arr_borrow = arr.borrow();
|
|
455
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
456
|
+
let result = cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
457
|
+
if result.is_truthy() {
|
|
458
|
+
return Value::Number(i as f64);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
Value::Number(-1.0)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
pub fn some(arr: &Value, callback: &Value) -> Value {
|
|
466
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
467
|
+
for (i, &n) in data.iter().enumerate() {
|
|
468
|
+
if cb.call(&[Value::Number(n), Value::Number(i as f64)]).is_truthy() {
|
|
469
|
+
return Value::Bool(true);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return Value::Bool(false);
|
|
473
|
+
}
|
|
474
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
475
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
476
|
+
let arr_borrow = arr.borrow();
|
|
477
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
478
|
+
let result = cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
479
|
+
if result.is_truthy() {
|
|
480
|
+
return Value::Bool(true);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
Value::Bool(false)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
pub fn every(arr: &Value, callback: &Value) -> Value {
|
|
488
|
+
if let Some((data, cb)) = packed_snapshot(arr, callback) {
|
|
489
|
+
for (i, &n) in data.iter().enumerate() {
|
|
490
|
+
if !cb.call(&[Value::Number(n), Value::Number(i as f64)]).is_truthy() {
|
|
491
|
+
return Value::Bool(false);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return Value::Bool(true);
|
|
495
|
+
}
|
|
496
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
497
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
498
|
+
let arr_borrow = arr.borrow();
|
|
499
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
500
|
+
let result = cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
501
|
+
if !result.is_truthy() {
|
|
502
|
+
return Value::Bool(false);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
Value::Bool(true)
|
|
506
|
+
} else {
|
|
507
|
+
Value::Bool(false)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
pub fn flat_map(arr: &Value, callback: &Value) -> Value {
|
|
512
|
+
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
513
|
+
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
514
|
+
let arr_borrow = arr.borrow();
|
|
515
|
+
let mut result: Vec<Value> = Vec::new();
|
|
516
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
517
|
+
let mapped = cb.call(&[v.clone(), Value::Number(i as f64)]);
|
|
518
|
+
if let Value::Array(inner) = mapped {
|
|
519
|
+
result.extend(inner.borrow().iter().cloned());
|
|
520
|
+
} else {
|
|
521
|
+
result.push(mapped);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
Value::Array(VmRef::new(result))
|
|
525
|
+
} else {
|
|
526
|
+
Value::Null
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
|
|
531
|
+
where
|
|
532
|
+
F: FnMut(&Value, &Value) -> std::cmp::Ordering,
|
|
533
|
+
{
|
|
534
|
+
if let Value::Array(arr) = arr {
|
|
535
|
+
arr.borrow_mut().sort_by(cmp);
|
|
536
|
+
Value::Array(arr.clone())
|
|
537
|
+
} else {
|
|
538
|
+
Value::Null
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
pub fn sort_default(arr: &Value) -> Value {
|
|
543
|
+
sort_by_impl(arr, |a, b| {
|
|
544
|
+
a.to_display_string().cmp(&b.to_display_string())
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
549
|
+
if let (Value::Array(arr), Value::Function(cmp_fn)) = (arr, comparator) {
|
|
550
|
+
let mut arr_mut = arr.borrow_mut();
|
|
551
|
+
let len = arr_mut.len();
|
|
552
|
+
let mut indices: Vec<usize> = (0..len).collect();
|
|
553
|
+
let mut elements: Vec<Value> = std::mem::take(&mut *arr_mut);
|
|
554
|
+
let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
|
|
555
|
+
|
|
556
|
+
indices.sort_by(|&a, &b| {
|
|
557
|
+
args_buf[0] = elements[a].clone();
|
|
558
|
+
args_buf[1] = elements[b].clone();
|
|
559
|
+
match cmp_fn.call(&args_buf) {
|
|
560
|
+
Value::Number(n) if n < 0.0 => std::cmp::Ordering::Less,
|
|
561
|
+
Value::Number(n) if n > 0.0 => std::cmp::Ordering::Greater,
|
|
562
|
+
_ => std::cmp::Ordering::Equal,
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
*arr_mut = indices
|
|
567
|
+
.into_iter()
|
|
568
|
+
.map(|i| std::mem::replace(&mut elements[i], Value::Null))
|
|
569
|
+
.collect();
|
|
570
|
+
drop(arr_mut);
|
|
571
|
+
Value::Array(arr.clone())
|
|
572
|
+
} else {
|
|
573
|
+
Value::Null
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
fn num_cmp(a: &Value, b: &Value, asc: bool) -> std::cmp::Ordering {
|
|
578
|
+
let (na, nb) = match (a, b) {
|
|
579
|
+
(Value::Number(a), Value::Number(b)) => (*a, *b),
|
|
580
|
+
_ => (f64::NAN, f64::NAN),
|
|
581
|
+
};
|
|
582
|
+
let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
|
|
583
|
+
if asc {
|
|
584
|
+
cmp
|
|
585
|
+
} else {
|
|
586
|
+
cmp.reverse()
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
pub fn sort_numeric_asc(arr: &Value) -> Value {
|
|
591
|
+
sort_numeric_impl(arr, true)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
pub fn sort_numeric_desc(arr: &Value) -> Value {
|
|
595
|
+
sort_numeric_impl(arr, false)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/// Numeric sort. When every element is a number, extract to unboxed `f64`,
|
|
599
|
+
/// `sort_unstable` (faster than the stable comparator sort, and stability is
|
|
600
|
+
/// irrelevant for equal numbers), then write back — no per-comparison `Value`
|
|
601
|
+
/// match. Mixed arrays fall back to the comparator path.
|
|
602
|
+
fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
603
|
+
// NumberArray fast path: sort the Vec<f64> directly — no unbox pass, no rebox.
|
|
604
|
+
if let Value::NumberArray(a) = arr {
|
|
605
|
+
let mut g = a.borrow_mut();
|
|
606
|
+
if asc {
|
|
607
|
+
g.sort_unstable_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
|
|
608
|
+
} else {
|
|
609
|
+
g.sort_unstable_by(|x, y| y.partial_cmp(x).unwrap_or(std::cmp::Ordering::Equal));
|
|
610
|
+
}
|
|
611
|
+
return Value::NumberArray(a.clone());
|
|
612
|
+
}
|
|
613
|
+
if let Value::Array(a) = arr {
|
|
614
|
+
{
|
|
615
|
+
let mut g = a.borrow_mut();
|
|
616
|
+
if g.iter().all(|v| matches!(v, Value::Number(_))) {
|
|
617
|
+
let mut nums: Vec<f64> = g
|
|
618
|
+
.iter()
|
|
619
|
+
.map(|v| match v {
|
|
620
|
+
Value::Number(n) => *n,
|
|
621
|
+
_ => f64::NAN,
|
|
622
|
+
})
|
|
623
|
+
.collect();
|
|
624
|
+
if asc {
|
|
625
|
+
nums.sort_unstable_by(|x, y| {
|
|
626
|
+
x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal)
|
|
627
|
+
});
|
|
628
|
+
} else {
|
|
629
|
+
nums.sort_unstable_by(|x, y| {
|
|
630
|
+
y.partial_cmp(x).unwrap_or(std::cmp::Ordering::Equal)
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
for (slot, n) in g.iter_mut().zip(nums) {
|
|
634
|
+
*slot = Value::Number(n);
|
|
635
|
+
}
|
|
636
|
+
return Value::Array(a.clone());
|
|
637
|
+
}
|
|
638
|
+
g.sort_by(|x, y| num_cmp(x, y, asc));
|
|
639
|
+
}
|
|
640
|
+
Value::Array(a.clone())
|
|
641
|
+
} else {
|
|
642
|
+
Value::Null
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/// Sort array of objects by numeric property: arr.sort((a,b)=>a.prop-b.prop)
|
|
647
|
+
pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
|
|
648
|
+
let prop_arc = std::sync::Arc::from(prop);
|
|
649
|
+
sort_by_impl(arr, move |a, b| {
|
|
650
|
+
let na = get_prop_number(a, &prop_arc);
|
|
651
|
+
let nb = get_prop_number(b, &prop_arc);
|
|
652
|
+
let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
|
|
653
|
+
if asc {
|
|
654
|
+
cmp
|
|
655
|
+
} else {
|
|
656
|
+
cmp.reverse()
|
|
657
|
+
}
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
fn get_prop_number(v: &Value, prop: &std::sync::Arc<str>) -> f64 {
|
|
662
|
+
match v {
|
|
663
|
+
Value::Object(o) => o
|
|
664
|
+
.borrow()
|
|
665
|
+
.strings
|
|
666
|
+
.get(prop.as_ref())
|
|
667
|
+
.map(|v| v.as_number().unwrap_or(f64::NAN))
|
|
668
|
+
.unwrap_or(f64::NAN),
|
|
669
|
+
// `.length` is a *computed* property (not a stored map entry) for strings and arrays.
|
|
670
|
+
// The fused `(a,b)=>a.length-b.length` sort path must compute it the same way
|
|
671
|
+
// `get_member` does, otherwise it returns NaN, every comparison collapses to Equal,
|
|
672
|
+
// and the array is left unsorted. Mirror get_member's length semantics here.
|
|
673
|
+
Value::String(s) if prop.as_ref() == "length" => s.chars().count() as f64,
|
|
674
|
+
Value::Array(a) if prop.as_ref() == "length" => a.borrow().len() as f64,
|
|
675
|
+
_ => f64::NAN,
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
#[cfg(test)]
|
|
680
|
+
mod packed_hof_tests {
|
|
681
|
+
//! The packed (`NumberArray`) HOF fast paths must be observably IDENTICAL to the boxed path —
|
|
682
|
+
//! same element + index callback args, same result shape — since cross-backend parity depends
|
|
683
|
+
//! on it. These run with packing semantics directly (the helpers don't read the env flag; a
|
|
684
|
+
//! `NumberArray` value is enough to take the fast path).
|
|
685
|
+
use super::*;
|
|
686
|
+
use tishlang_core::Value;
|
|
687
|
+
|
|
688
|
+
fn na(xs: &[f64]) -> Value {
|
|
689
|
+
Value::NumberArray(VmRef::new(xs.to_vec()))
|
|
690
|
+
}
|
|
691
|
+
fn nums(v: &Value) -> Vec<f64> {
|
|
692
|
+
match v {
|
|
693
|
+
Value::Array(a) => a.borrow().iter().map(|e| e.as_number().unwrap_or(f64::NAN)).collect(),
|
|
694
|
+
Value::NumberArray(a) => a.borrow().clone(),
|
|
695
|
+
_ => vec![],
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
fn cb_num(f: fn(f64, f64) -> f64) -> Value {
|
|
699
|
+
Value::native(move |a: &[Value]| {
|
|
700
|
+
Value::Number(f(a[0].as_number().unwrap_or(0.0), a[1].as_number().unwrap_or(0.0)))
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
fn cb_pred(f: fn(f64, f64) -> bool) -> Value {
|
|
704
|
+
Value::native(move |a: &[Value]| {
|
|
705
|
+
Value::Bool(f(a[0].as_number().unwrap_or(0.0), a[1].as_number().unwrap_or(0.0)))
|
|
706
|
+
})
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
#[test]
|
|
710
|
+
fn reduce_packed() {
|
|
711
|
+
let n = na(&[3.0, 1.0, 4.0, 1.0, 5.0]);
|
|
712
|
+
let add = cb_num(|acc, x| acc + x);
|
|
713
|
+
// With init.
|
|
714
|
+
assert_eq!(reduce(&n, &add, &Value::Number(0.0)).as_number(), Some(14.0));
|
|
715
|
+
// No init → first element seeds, scan from index 1 (same total here).
|
|
716
|
+
assert_eq!(reduce(&n, &add, &Value::Null).as_number(), Some(14.0));
|
|
717
|
+
// Index arg: callback (acc, _elem, index) — sum the indices 0..5 = 10.
|
|
718
|
+
let sum_idx = Value::native(|a: &[Value]| {
|
|
719
|
+
Value::Number(a[0].as_number().unwrap() + a[2].as_number().unwrap())
|
|
720
|
+
});
|
|
721
|
+
assert_eq!(reduce(&n, &sum_idx, &Value::Number(0.0)).as_number(), Some(10.0));
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
#[test]
|
|
725
|
+
fn map_filter_stay_packed() {
|
|
726
|
+
let n = na(&[3.0, 1.0, 4.0, 1.0, 5.0]);
|
|
727
|
+
// Numeric map → packed NumberArray result (chains stay packed), with correct values.
|
|
728
|
+
let m = map(&n, &cb_num(|x, _i| x * 2.0));
|
|
729
|
+
assert!(matches!(m, Value::NumberArray(_)), "numeric map should stay packed");
|
|
730
|
+
assert_eq!(nums(&m), vec![6.0, 2.0, 8.0, 2.0, 10.0]);
|
|
731
|
+
// filter keeps a subset of the input f64s → always packed.
|
|
732
|
+
let f = filter(&n, &cb_pred(|x, _i| x > 2.0));
|
|
733
|
+
assert!(matches!(f, Value::NumberArray(_)), "filter should stay packed");
|
|
734
|
+
assert_eq!(nums(&f), vec![3.0, 4.0, 5.0]);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
#[test]
|
|
738
|
+
fn map_deopts_to_boxed_on_non_numeric() {
|
|
739
|
+
let n = na(&[1.0, 2.0, 3.0]);
|
|
740
|
+
// Callback returns a string for the middle element → deopt to a boxed array, preserving order
|
|
741
|
+
// (callback runs once per element).
|
|
742
|
+
let cb = Value::native(|a: &[Value]| {
|
|
743
|
+
let x = a[0].as_number().unwrap();
|
|
744
|
+
if x == 2.0 { Value::String("two".into()) } else { Value::Number(x * 10.0) }
|
|
745
|
+
});
|
|
746
|
+
match &map(&n, &cb) {
|
|
747
|
+
Value::Array(a) => {
|
|
748
|
+
let b = a.borrow();
|
|
749
|
+
assert_eq!(b.len(), 3);
|
|
750
|
+
assert_eq!(b[0].as_number(), Some(10.0));
|
|
751
|
+
assert!(matches!(&b[1], Value::String(s) if s.as_str() == "two"));
|
|
752
|
+
assert_eq!(b[2].as_number(), Some(30.0));
|
|
753
|
+
}
|
|
754
|
+
_ => panic!("mixed-result map must be a boxed array"),
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
#[test]
|
|
759
|
+
fn map_filter_empty_stays_boxed() {
|
|
760
|
+
let n = na(&[1.0, 2.0, 3.0]);
|
|
761
|
+
// All rejected → empty boxed array (empty arrays stay general-purpose containers).
|
|
762
|
+
assert!(matches!(filter(&n, &cb_pred(|_x, _i| false)), Value::Array(_)));
|
|
763
|
+
// Empty input → empty boxed array.
|
|
764
|
+
assert!(matches!(map(&na(&[]), &cb_num(|x, _i| x)), Value::Array(_)));
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
#[test]
|
|
768
|
+
fn scan_packed() {
|
|
769
|
+
let n = na(&[3.0, 1.0, 4.0, 1.0, 5.0]);
|
|
770
|
+
assert!(matches!(some(&n, &cb_pred(|x, _i| x > 4.0)), Value::Bool(true)));
|
|
771
|
+
assert!(matches!(some(&n, &cb_pred(|x, _i| x > 9.0)), Value::Bool(false)));
|
|
772
|
+
assert!(matches!(every(&n, &cb_pred(|x, _i| x > 0.0)), Value::Bool(true)));
|
|
773
|
+
assert!(matches!(every(&n, &cb_pred(|x, _i| x > 2.0)), Value::Bool(false)));
|
|
774
|
+
// first element > 3 is 4.0 at index 2.
|
|
775
|
+
assert_eq!(find(&n, &cb_pred(|x, _i| x > 3.0)).as_number(), Some(4.0));
|
|
776
|
+
assert_eq!(find_index(&n, &cb_pred(|x, _i| x > 3.0)).as_number(), Some(2.0));
|
|
777
|
+
assert_eq!(find_index(&n, &cb_pred(|x, _i| x > 99.0)).as_number(), Some(-1.0));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
#[test]
|
|
781
|
+
fn for_each_packed_passes_element_and_index() {
|
|
782
|
+
use std::sync::{Arc, Mutex};
|
|
783
|
+
let n = na(&[3.0, 1.0, 4.0, 1.0, 5.0]);
|
|
784
|
+
let acc = Arc::new(Mutex::new(0.0f64));
|
|
785
|
+
let a2 = acc.clone();
|
|
786
|
+
let collect = Value::native(move |a: &[Value]| {
|
|
787
|
+
*a2.lock().unwrap() += a[0].as_number().unwrap() + a[1].as_number().unwrap();
|
|
788
|
+
Value::Null
|
|
789
|
+
});
|
|
790
|
+
assert!(matches!(for_each(&n, &collect), Value::Null));
|
|
791
|
+
// sum(elems)=14 + sum(idx 0..5)=10.
|
|
792
|
+
assert_eq!(*acc.lock().unwrap(), 24.0);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#[test]
|
|
796
|
+
fn non_function_callback_falls_through() {
|
|
797
|
+
// A NumberArray with a non-callable 2nd arg must not take the fast path; mirrors the boxed
|
|
798
|
+
// path's `Value::Null` (map/filter) without panicking.
|
|
799
|
+
let n = na(&[1.0, 2.0]);
|
|
800
|
+
assert!(matches!(map(&n, &Value::Number(1.0)), Value::Null));
|
|
801
|
+
assert!(matches!(filter(&n, &Value::Null), Value::Null));
|
|
802
|
+
}
|
|
803
|
+
}
|