@tishlang/tish-format 2.16.13 → 2.35.0
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/bin/tish-format +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +12 -2
- package/crates/tish/tests/fixtures/fs_parity_callbacks.tish +22 -0
- package/crates/tish/tests/fixtures/fs_parity_promises.tish +21 -0
- package/crates/tish/tests/fixtures/fs_parity_sync.tish +20 -0
- package/crates/tish/tests/fs_parity.rs +103 -0
- package/crates/tish/tests/shortcircuit.rs +3 -3
- package/crates/tish_ast/src/ast.rs +77 -3
- package/crates/tish_builtins/Cargo.toml +1 -0
- package/crates/tish_builtins/src/array.rs +195 -16
- package/crates/tish_builtins/src/collections.rs +37 -2
- package/crates/tish_builtins/src/globals.rs +215 -25
- package/crates/tish_builtins/src/math.rs +65 -7
- package/crates/tish_builtins/src/number.rs +173 -0
- package/crates/tish_builtins/src/string.rs +168 -15
- package/crates/tish_bytecode/src/compiler.rs +56 -4
- package/crates/tish_bytecode/src/opcode.rs +4 -2
- package/crates/tish_compile/src/codegen.rs +513 -44
- package/crates/tish_compile/src/infer.rs +26 -11
- package/crates/tish_compile/src/resolve.rs +87 -10
- package/crates/tish_compile_js/src/codegen.rs +82 -24
- package/crates/tish_compile_js/src/tests_jsx.rs +27 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +17 -2
- package/crates/tish_core/src/json.rs +5 -1
- package/crates/tish_core/src/lib.rs +48 -2
- package/crates/tish_core/src/uri.rs +45 -0
- package/crates/tish_core/src/value.rs +6 -0
- package/crates/tish_eval/Cargo.toml +2 -1
- package/crates/tish_eval/src/eval.rs +655 -27
- package/crates/tish_eval/src/natives.rs +264 -18
- package/crates/tish_eval/src/regex.rs +47 -1
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/crates/tish_fmt/src/lib.rs +23 -4
- package/crates/tish_opt/src/lib.rs +20 -1
- package/crates/tish_parser/src/lib.rs +41 -0
- package/crates/tish_parser/src/parser.rs +180 -11
- package/crates/tish_resolve/src/lib.rs +66 -5
- package/crates/tish_runtime/Cargo.toml +5 -1
- package/crates/tish_runtime/src/fs_ext.rs +359 -0
- package/crates/tish_runtime/src/http_hyper.rs +41 -18
- package/crates/tish_runtime/src/lib.rs +128 -9
- package/crates/tish_ui/src/jsx.rs +18 -3
- package/crates/tish_vm/src/jit.rs +1 -1
- package/crates/tish_vm/src/vm.rs +270 -60
- package/package.json +1 -1
- 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
package/crates/tish_vm/src/vm.rs
CHANGED
|
@@ -180,28 +180,65 @@ pub fn all_compiled_capabilities() -> HashSet<String> {
|
|
|
180
180
|
fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str) -> Option<Value> {
|
|
181
181
|
#[cfg(feature = "fs")]
|
|
182
182
|
if spec == "tish:fs" && cap_allows(enabled, "fs") {
|
|
183
|
+
use tishlang_runtime::fs_ext as fx;
|
|
183
184
|
return match export_name {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
"
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
185
|
+
// tish names → the dual fs_ext functions (sync, or Node callback if a fn is passed)
|
|
186
|
+
"readFile" => Some(Value::native(|a: &[Value]| fx::read_file(a))),
|
|
187
|
+
"writeFile" => Some(Value::native(|a: &[Value]| fx::write_file(a))),
|
|
188
|
+
"fileExists" => Some(Value::native(|a: &[Value]| tishlang_runtime::file_exists(a))),
|
|
189
|
+
"isDir" => Some(Value::native(|a: &[Value]| tishlang_runtime::is_dir(a))),
|
|
190
|
+
"readDir" => Some(Value::native(|a: &[Value]| fx::readdir(a))),
|
|
191
|
+
"readFileBytes" => Some(Value::native(|a: &[Value]| fx::read_file_bytes(a))),
|
|
192
|
+
// Node-compatible names + new methods (sync) — issue #122
|
|
193
|
+
"readFileSync" => Some(Value::native(|a: &[Value]| fx::read_file(a))),
|
|
194
|
+
"readFileBytesSync" => Some(Value::native(|a: &[Value]| fx::read_file_bytes(a))),
|
|
195
|
+
"writeFileSync" => Some(Value::native(|a: &[Value]| fx::write_file(a))),
|
|
196
|
+
"appendFile" | "appendFileSync" => Some(Value::native(|a: &[Value]| fx::append_file(a))),
|
|
197
|
+
"existsSync" => Some(Value::native(|a: &[Value]| fx::exists(a))),
|
|
198
|
+
"mkdir" | "mkdirSync" => Some(Value::native(|a: &[Value]| fx::mkdir(a))),
|
|
199
|
+
"readdir" | "readdirSync" => Some(Value::native(|a: &[Value]| fx::readdir(a))),
|
|
200
|
+
"stat" | "statSync" => Some(Value::native(|a: &[Value]| fx::stat(a))),
|
|
201
|
+
"lstat" | "lstatSync" => Some(Value::native(|a: &[Value]| fx::lstat(a))),
|
|
202
|
+
"rm" | "rmSync" => Some(Value::native(|a: &[Value]| fx::rm(a))),
|
|
203
|
+
"rmdir" | "rmdirSync" => Some(Value::native(|a: &[Value]| fx::rmdir(a))),
|
|
204
|
+
"unlink" | "unlinkSync" => Some(Value::native(|a: &[Value]| fx::unlink(a))),
|
|
205
|
+
"rename" | "renameSync" => Some(Value::native(|a: &[Value]| fx::rename(a))),
|
|
206
|
+
"copyFile" | "copyFileSync" => Some(Value::native(|a: &[Value]| fx::copy_file(a))),
|
|
207
|
+
"realpath" | "realpathSync" => Some(Value::native(|a: &[Value]| fx::realpath(a))),
|
|
208
|
+
"readlink" | "readlinkSync" => Some(Value::native(|a: &[Value]| fx::readlink(a))),
|
|
209
|
+
"truncate" | "truncateSync" => Some(Value::native(|a: &[Value]| fx::truncate(a))),
|
|
210
|
+
"mkdtemp" | "mkdtempSync" => Some(Value::native(|a: &[Value]| fx::mkdtemp(a))),
|
|
211
|
+
"cp" | "cpSync" => Some(Value::native(|a: &[Value]| fx::cp(a))),
|
|
212
|
+
"access" | "accessSync" => Some(Value::native(|a: &[Value]| fx::access(a))),
|
|
213
|
+
"constants" => Some(fx::constants()),
|
|
214
|
+
_ => None,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
#[cfg(feature = "fs")]
|
|
218
|
+
if spec == "tish:fs/promises" && cap_allows(enabled, "fs") {
|
|
219
|
+
use tishlang_runtime::fs_ext as fx;
|
|
220
|
+
return match export_name {
|
|
221
|
+
"readFile" => Some(Value::native(|a: &[Value]| fx::read_file_promise(a))),
|
|
222
|
+
"readFileBytes" => Some(Value::native(|a: &[Value]| fx::read_file_bytes_promise(a))),
|
|
223
|
+
"writeFile" => Some(Value::native(|a: &[Value]| fx::write_file_promise(a))),
|
|
224
|
+
"appendFile" => Some(Value::native(|a: &[Value]| fx::append_file_promise(a))),
|
|
225
|
+
"mkdir" => Some(Value::native(|a: &[Value]| fx::mkdir_promise(a))),
|
|
226
|
+
"readdir" => Some(Value::native(|a: &[Value]| fx::readdir_promise(a))),
|
|
227
|
+
"stat" => Some(Value::native(|a: &[Value]| fx::stat_promise(a))),
|
|
228
|
+
"lstat" => Some(Value::native(|a: &[Value]| fx::lstat_promise(a))),
|
|
229
|
+
"rm" => Some(Value::native(|a: &[Value]| fx::rm_promise(a))),
|
|
230
|
+
"rmdir" => Some(Value::native(|a: &[Value]| fx::rmdir_promise(a))),
|
|
231
|
+
"unlink" => Some(Value::native(|a: &[Value]| fx::unlink_promise(a))),
|
|
232
|
+
"rename" => Some(Value::native(|a: &[Value]| fx::rename_promise(a))),
|
|
233
|
+
"copyFile" => Some(Value::native(|a: &[Value]| fx::copy_file_promise(a))),
|
|
234
|
+
"realpath" => Some(Value::native(|a: &[Value]| fx::realpath_promise(a))),
|
|
235
|
+
"readlink" => Some(Value::native(|a: &[Value]| fx::readlink_promise(a))),
|
|
236
|
+
"truncate" => Some(Value::native(|a: &[Value]| fx::truncate_promise(a))),
|
|
237
|
+
"mkdtemp" => Some(Value::native(|a: &[Value]| fx::mkdtemp_promise(a))),
|
|
238
|
+
"cp" => Some(Value::native(|a: &[Value]| fx::cp_promise(a))),
|
|
239
|
+
"access" => Some(Value::native(|a: &[Value]| fx::access_promise(a))),
|
|
240
|
+
"exists" => Some(Value::native(|a: &[Value]| fx::exists_promise(a))),
|
|
241
|
+
"constants" => Some(fx::constants()),
|
|
205
242
|
_ => None,
|
|
206
243
|
};
|
|
207
244
|
}
|
|
@@ -537,6 +574,13 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
537
574
|
"pow".into(),
|
|
538
575
|
Value::native(|args: &[Value]| math_builtins::pow(args)),
|
|
539
576
|
);
|
|
577
|
+
// `imul` delegates to the shared builtin (ToInt32 + wrapping i32 multiply) so the vm never
|
|
578
|
+
// diverges from interp/native (#247). Was previously absent from the vm `Math` object, so
|
|
579
|
+
// `Math.imul(...)` threw "Call of non-function" on the bytecode VM.
|
|
580
|
+
math.insert(
|
|
581
|
+
"imul".into(),
|
|
582
|
+
Value::native(|args: &[Value]| math_builtins::imul(args)),
|
|
583
|
+
);
|
|
540
584
|
math.insert(
|
|
541
585
|
"sin".into(),
|
|
542
586
|
Value::native(|args: &[Value]| math_builtins::sin(args)),
|
|
@@ -597,13 +641,12 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
597
641
|
Value::Number(n.acos())
|
|
598
642
|
}),
|
|
599
643
|
);
|
|
644
|
+
// hypot delegates to the shared builtin (variadic, NaN/Infinity-correct) so the vm never diverges
|
|
645
|
+
// from interp/native (#247). The old inline `filter_map(as_number)` dropped non-number args
|
|
646
|
+
// instead of propagating NaN.
|
|
600
647
|
math.insert(
|
|
601
648
|
"hypot".into(),
|
|
602
|
-
Value::native(|args: &[Value]|
|
|
603
|
-
let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
|
|
604
|
-
let sum_sq: f64 = nums.iter().map(|n| n * n).sum();
|
|
605
|
-
Value::Number(sum_sq.sqrt())
|
|
606
|
-
}),
|
|
649
|
+
Value::native(|args: &[Value]| math_builtins::hypot(args)),
|
|
607
650
|
);
|
|
608
651
|
// Hyperbolic, inverse-hyperbolic, cbrt and base-2/10 logs. Like the trig block above
|
|
609
652
|
// these aren't in `math_builtins`, and on the wasm/native VM there is no host `Math`
|
|
@@ -628,6 +671,10 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
628
671
|
math_unary!("cbrt", cbrt);
|
|
629
672
|
math_unary!("log2", log2);
|
|
630
673
|
math_unary!("log10", log10);
|
|
674
|
+
math_unary!("expm1", exp_m1);
|
|
675
|
+
math_unary!("log1p", ln_1p);
|
|
676
|
+
math.insert("clz32".into(), Value::native(|a: &[Value]| math_builtins::clz32(a)));
|
|
677
|
+
math.insert("fround".into(), Value::native(|a: &[Value]| math_builtins::fround(a)));
|
|
631
678
|
math.insert("PI".into(), Value::Number(std::f64::consts::PI));
|
|
632
679
|
math.insert("E".into(), Value::Number(std::f64::consts::E));
|
|
633
680
|
g.insert("Math".into(), value_object_from_map(math));
|
|
@@ -668,6 +715,18 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
668
715
|
"decodeURI".into(),
|
|
669
716
|
Value::native(|args: &[Value]| globals_builtins::decode_uri(args)),
|
|
670
717
|
);
|
|
718
|
+
g.insert(
|
|
719
|
+
"encodeURIComponent".into(),
|
|
720
|
+
Value::native(|args: &[Value]| globals_builtins::encode_uri_component(args)),
|
|
721
|
+
);
|
|
722
|
+
g.insert(
|
|
723
|
+
"decodeURIComponent".into(),
|
|
724
|
+
Value::native(|args: &[Value]| globals_builtins::decode_uri_component(args)),
|
|
725
|
+
);
|
|
726
|
+
g.insert(
|
|
727
|
+
"structuredClone".into(),
|
|
728
|
+
Value::native(|args: &[Value]| globals_builtins::structured_clone(args)),
|
|
729
|
+
);
|
|
671
730
|
g.insert(
|
|
672
731
|
"htmlEscape".into(),
|
|
673
732
|
Value::native(|args: &[Value]| {
|
|
@@ -769,6 +828,18 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
769
828
|
"assign".into(),
|
|
770
829
|
Value::native(|args: &[Value]| globals_builtins::object_assign(args)),
|
|
771
830
|
);
|
|
831
|
+
object_methods.insert(
|
|
832
|
+
"is".into(),
|
|
833
|
+
Value::native(|args: &[Value]| globals_builtins::object_is(args)),
|
|
834
|
+
);
|
|
835
|
+
object_methods.insert(
|
|
836
|
+
"hasOwn".into(),
|
|
837
|
+
Value::native(|args: &[Value]| globals_builtins::object_has_own(args)),
|
|
838
|
+
);
|
|
839
|
+
object_methods.insert(
|
|
840
|
+
"getOwnPropertyNames".into(),
|
|
841
|
+
Value::native(|args: &[Value]| globals_builtins::object_keys(args)),
|
|
842
|
+
);
|
|
772
843
|
object_methods.insert(
|
|
773
844
|
"fromEntries".into(),
|
|
774
845
|
Value::native(|args: &[Value]| globals_builtins::object_from_entries(args)),
|
|
@@ -794,6 +865,14 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
794
865
|
"isArray".into(),
|
|
795
866
|
Value::native(|args: &[Value]| globals_builtins::array_is_array(args)),
|
|
796
867
|
);
|
|
868
|
+
array_static.insert(
|
|
869
|
+
"of".into(),
|
|
870
|
+
Value::native(|args: &[Value]| globals_builtins::array_of(args)),
|
|
871
|
+
);
|
|
872
|
+
array_static.insert(
|
|
873
|
+
"from".into(),
|
|
874
|
+
Value::native(|args: &[Value]| globals_builtins::array_from(args)),
|
|
875
|
+
);
|
|
797
876
|
array_static.insert(
|
|
798
877
|
Arc::from("__call"),
|
|
799
878
|
Value::native(|args: &[Value]| construct_builtin::array_construct(args)),
|
|
@@ -810,12 +889,26 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
810
889
|
string_static.insert(Arc::from("__call"), string_convert_fn);
|
|
811
890
|
g.insert("String".into(), value_object_from_map(string_static));
|
|
812
891
|
|
|
813
|
-
// Number(value) coercion as a callable global (issue #36).
|
|
892
|
+
// Number(value) coercion as a callable global (issue #36) + the `Number.*` statics.
|
|
814
893
|
let mut number_static = ObjectMap::default();
|
|
815
894
|
number_static.insert(
|
|
816
895
|
Arc::from("__call"),
|
|
817
896
|
Value::native(|args: &[Value]| globals_builtins::number_convert(args)),
|
|
818
897
|
);
|
|
898
|
+
number_static.insert(Arc::from("isInteger"), Value::native(|a: &[Value]| globals_builtins::number_is_integer(a)));
|
|
899
|
+
number_static.insert(Arc::from("isSafeInteger"), Value::native(|a: &[Value]| globals_builtins::number_is_safe_integer(a)));
|
|
900
|
+
number_static.insert(Arc::from("isNaN"), Value::native(|a: &[Value]| globals_builtins::number_is_nan(a)));
|
|
901
|
+
number_static.insert(Arc::from("isFinite"), Value::native(|a: &[Value]| globals_builtins::number_is_finite(a)));
|
|
902
|
+
number_static.insert(Arc::from("parseInt"), Value::native(|a: &[Value]| globals_builtins::parse_int(a)));
|
|
903
|
+
number_static.insert(Arc::from("parseFloat"), Value::native(|a: &[Value]| globals_builtins::parse_float(a)));
|
|
904
|
+
number_static.insert(Arc::from("MAX_SAFE_INTEGER"), Value::Number(9_007_199_254_740_991.0));
|
|
905
|
+
number_static.insert(Arc::from("MIN_SAFE_INTEGER"), Value::Number(-9_007_199_254_740_991.0));
|
|
906
|
+
number_static.insert(Arc::from("EPSILON"), Value::Number(f64::EPSILON));
|
|
907
|
+
number_static.insert(Arc::from("MAX_VALUE"), Value::Number(f64::MAX));
|
|
908
|
+
number_static.insert(Arc::from("MIN_VALUE"), Value::Number(5e-324));
|
|
909
|
+
number_static.insert(Arc::from("POSITIVE_INFINITY"), Value::Number(f64::INFINITY));
|
|
910
|
+
number_static.insert(Arc::from("NEGATIVE_INFINITY"), Value::Number(f64::NEG_INFINITY));
|
|
911
|
+
number_static.insert(Arc::from("NaN"), Value::Number(f64::NAN));
|
|
819
912
|
g.insert("Number".into(), value_object_from_map(number_static));
|
|
820
913
|
|
|
821
914
|
// JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
|
|
@@ -1383,7 +1476,7 @@ mod hof_fusion {
|
|
|
1383
1476
|
/// (which would pass a `Value::Bool`, not a number). No-initial-value semantics match the generic
|
|
1384
1477
|
/// path: absent init (`Value::Null`) with a non-empty array seeds `acc = data[0]` and scans from
|
|
1385
1478
|
/// index 1; an empty array with no init throws (bail to the generic path, which raises it).
|
|
1386
|
-
pub(super) fn reduce(arr: &Value, cb: &Value,
|
|
1479
|
+
pub(super) fn reduce(arr: &Value, cb: &Value, initial: Option<&Value>) -> Option<Value> {
|
|
1387
1480
|
let nf = fused_numeric_fn(cb)?;
|
|
1388
1481
|
if nf.arity() > 3 || nf.result_is_bool() {
|
|
1389
1482
|
return None;
|
|
@@ -1392,11 +1485,11 @@ mod hof_fusion {
|
|
|
1392
1485
|
let arity = nf.arity();
|
|
1393
1486
|
// Determine seed. Reduce with no initial value AND an empty array is a TypeError in JS — let
|
|
1394
1487
|
// the generic path produce that exact throw rather than replicate it here.
|
|
1395
|
-
let (start, mut acc) = match
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
Value::Number(n) => (0usize, *n),
|
|
1399
|
-
_ => return None, // non-numeric
|
|
1488
|
+
let (start, mut acc) = match initial {
|
|
1489
|
+
None if !data.is_empty() => (1usize, data[0]),
|
|
1490
|
+
None => return None, // empty + no init → generic path throws
|
|
1491
|
+
Some(Value::Number(n)) => (0usize, *n),
|
|
1492
|
+
Some(_) => return None, // non-numeric/explicit-null init → generic path (acc wouldn't stay f64)
|
|
1400
1493
|
};
|
|
1401
1494
|
let mut args = [0.0f64; 3];
|
|
1402
1495
|
for (i, &x) in data.iter().enumerate().skip(start) {
|
|
@@ -3823,13 +3916,21 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3823
3916
|
}),
|
|
3824
3917
|
"reduce" => make_native_fn(move |args| {
|
|
3825
3918
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3826
|
-
let init = args.get(1).cloned()
|
|
3919
|
+
let init = args.get(1).cloned(); // None = no initial value (empty array → throw)
|
|
3827
3920
|
#[cfg(not(target_arch = "wasm32"))]
|
|
3828
|
-
if let Some(v) = hof_fusion::reduce(&bv, &cb,
|
|
3921
|
+
if let Some(v) = hof_fusion::reduce(&bv, &cb, init.as_ref()) {
|
|
3829
3922
|
return v;
|
|
3830
3923
|
}
|
|
3831
|
-
arr_builtins::reduce(&bv, &cb,
|
|
3924
|
+
arr_builtins::reduce(&bv, &cb, init.as_ref())
|
|
3832
3925
|
}),
|
|
3926
|
+
"reduceRight" => make_native_fn(move |args| {
|
|
3927
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3928
|
+
let init = args.get(1).cloned();
|
|
3929
|
+
arr_builtins::reduce_right(&bv, &cb, init.as_ref())
|
|
3930
|
+
}),
|
|
3931
|
+
"keys" => make_native_fn(move |_| arr_builtins::keys(&bv)),
|
|
3932
|
+
"values" => make_native_fn(move |_| arr_builtins::values(&bv)),
|
|
3933
|
+
"entries" => make_native_fn(move |_| arr_builtins::entries(&bv)),
|
|
3833
3934
|
"forEach" => make_native_fn(move |args| {
|
|
3834
3935
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3835
3936
|
#[cfg(not(target_arch = "wasm32"))]
|
|
@@ -3879,6 +3980,21 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3879
3980
|
arr_builtins::flat_map(&bv, &cb)
|
|
3880
3981
|
}),
|
|
3881
3982
|
"reverse" => make_native_fn(move |_| arr_builtins::reverse(&bv)),
|
|
3983
|
+
"toReversed" => make_native_fn(move |_| arr_builtins::to_reversed(&bv)),
|
|
3984
|
+
"toSorted" => {
|
|
3985
|
+
make_native_fn(move |args| arr_builtins::to_sorted(&bv, args.first()))
|
|
3986
|
+
}
|
|
3987
|
+
"with" => make_native_fn(move |args| {
|
|
3988
|
+
let i = args.first().cloned().unwrap_or(Value::Null);
|
|
3989
|
+
let v = args.get(1).cloned().unwrap_or(Value::Null);
|
|
3990
|
+
arr_builtins::with(&bv, &i, &v)
|
|
3991
|
+
}),
|
|
3992
|
+
"toSpliced" => make_native_fn(move |args| {
|
|
3993
|
+
let start = args.first().cloned().unwrap_or(Value::Null);
|
|
3994
|
+
let dc = args.get(1).cloned();
|
|
3995
|
+
let items: Vec<Value> = args.iter().skip(2).cloned().collect();
|
|
3996
|
+
arr_builtins::to_spliced(&bv, &start, dc.as_ref(), &items)
|
|
3997
|
+
}),
|
|
3882
3998
|
"fill" => make_native_fn(move |args| {
|
|
3883
3999
|
let v = args.first().cloned().unwrap_or(Value::Null);
|
|
3884
4000
|
let s = args.get(1).cloned().unwrap_or(Value::Null);
|
|
@@ -3893,7 +4009,17 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3893
4009
|
"concat" => make_native_fn(move |args| arr_builtins::concat(&bv, args)),
|
|
3894
4010
|
"indexOf" => make_native_fn(move |args| {
|
|
3895
4011
|
let s = args.first().cloned().unwrap_or(Value::Null);
|
|
3896
|
-
arr_builtins::index_of(&bv, &s)
|
|
4012
|
+
arr_builtins::index_of(&bv, &s, args.get(1))
|
|
4013
|
+
}),
|
|
4014
|
+
"lastIndexOf" => make_native_fn(move |args| {
|
|
4015
|
+
let s = args.first().cloned().unwrap_or(Value::Null);
|
|
4016
|
+
arr_builtins::last_index_of(&bv, &s, args.get(1))
|
|
4017
|
+
}),
|
|
4018
|
+
"copyWithin" => make_native_fn(move |args| {
|
|
4019
|
+
let t = args.first().cloned().unwrap_or(Value::Null);
|
|
4020
|
+
let s = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4021
|
+
let e = args.get(2).cloned().unwrap_or(Value::Null);
|
|
4022
|
+
arr_builtins::copy_within(&bv, &t, &s, &e)
|
|
3897
4023
|
}),
|
|
3898
4024
|
"includes" => make_native_fn(move |args| {
|
|
3899
4025
|
let s = args.first().cloned().unwrap_or(Value::Null);
|
|
@@ -3966,7 +4092,17 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3966
4092
|
}),
|
|
3967
4093
|
"indexOf" => make_native_fn(move |args: &[Value]| {
|
|
3968
4094
|
let search = args.first().unwrap_or(&Value::Null);
|
|
3969
|
-
arr_builtins::index_of(&Value::Array(a_clone.clone()), search)
|
|
4095
|
+
arr_builtins::index_of(&Value::Array(a_clone.clone()), search, args.get(1))
|
|
4096
|
+
}),
|
|
4097
|
+
"lastIndexOf" => make_native_fn(move |args: &[Value]| {
|
|
4098
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
4099
|
+
arr_builtins::last_index_of(&Value::Array(a_clone.clone()), search, args.get(1))
|
|
4100
|
+
}),
|
|
4101
|
+
"copyWithin" => make_native_fn(move |args: &[Value]| {
|
|
4102
|
+
let t = args.first().cloned().unwrap_or(Value::Null);
|
|
4103
|
+
let s = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4104
|
+
let e = args.get(2).cloned().unwrap_or(Value::Null);
|
|
4105
|
+
arr_builtins::copy_within(&Value::Array(a_clone.clone()), &t, &s, &e)
|
|
3970
4106
|
}),
|
|
3971
4107
|
"includes" => make_native_fn(move |args: &[Value]| {
|
|
3972
4108
|
let search = args.first().unwrap_or(&Value::Null);
|
|
@@ -3995,14 +4131,22 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3995
4131
|
}),
|
|
3996
4132
|
"reduce" => make_native_fn(move |args: &[Value]| {
|
|
3997
4133
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3998
|
-
let init = args.get(1).cloned()
|
|
4134
|
+
let init = args.get(1).cloned(); // None = no initial value (empty array → throw)
|
|
3999
4135
|
let arr = Value::Array(a_clone.clone());
|
|
4000
4136
|
#[cfg(not(target_arch = "wasm32"))]
|
|
4001
|
-
if let Some(v) = hof_fusion::reduce(&arr, &cb,
|
|
4137
|
+
if let Some(v) = hof_fusion::reduce(&arr, &cb, init.as_ref()) {
|
|
4002
4138
|
return v;
|
|
4003
4139
|
}
|
|
4004
|
-
arr_builtins::reduce(&arr, &cb,
|
|
4140
|
+
arr_builtins::reduce(&arr, &cb, init.as_ref())
|
|
4005
4141
|
}),
|
|
4142
|
+
"reduceRight" => make_native_fn(move |args: &[Value]| {
|
|
4143
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
4144
|
+
let init = args.get(1).cloned();
|
|
4145
|
+
arr_builtins::reduce_right(&Value::Array(a_clone.clone()), &cb, init.as_ref())
|
|
4146
|
+
}),
|
|
4147
|
+
"keys" => make_native_fn(move |_: &[Value]| arr_builtins::keys(&Value::Array(a_clone.clone()))),
|
|
4148
|
+
"values" => make_native_fn(move |_: &[Value]| arr_builtins::values(&Value::Array(a_clone.clone()))),
|
|
4149
|
+
"entries" => make_native_fn(move |_: &[Value]| arr_builtins::entries(&Value::Array(a_clone.clone()))),
|
|
4006
4150
|
"forEach" => make_native_fn(move |args: &[Value]| {
|
|
4007
4151
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
4008
4152
|
let arr = Value::Array(a_clone.clone());
|
|
@@ -4070,6 +4214,28 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4070
4214
|
&items,
|
|
4071
4215
|
)
|
|
4072
4216
|
}),
|
|
4217
|
+
"toReversed" => make_native_fn(move |_: &[Value]| {
|
|
4218
|
+
arr_builtins::to_reversed(&Value::Array(a_clone.clone()))
|
|
4219
|
+
}),
|
|
4220
|
+
"toSorted" => make_native_fn(move |args: &[Value]| {
|
|
4221
|
+
arr_builtins::to_sorted(&Value::Array(a_clone.clone()), args.first())
|
|
4222
|
+
}),
|
|
4223
|
+
"with" => make_native_fn(move |args: &[Value]| {
|
|
4224
|
+
let i = args.first().cloned().unwrap_or(Value::Null);
|
|
4225
|
+
let v = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4226
|
+
arr_builtins::with(&Value::Array(a_clone.clone()), &i, &v)
|
|
4227
|
+
}),
|
|
4228
|
+
"toSpliced" => make_native_fn(move |args: &[Value]| {
|
|
4229
|
+
let start = args.first().unwrap_or(&Value::Null);
|
|
4230
|
+
let delete_count = args.get(1).map(|v| v as &Value);
|
|
4231
|
+
let items: Vec<Value> = args.get(2..).unwrap_or(&[]).to_vec();
|
|
4232
|
+
arr_builtins::to_spliced(
|
|
4233
|
+
&Value::Array(a_clone.clone()),
|
|
4234
|
+
start,
|
|
4235
|
+
delete_count,
|
|
4236
|
+
&items,
|
|
4237
|
+
)
|
|
4238
|
+
}),
|
|
4073
4239
|
_ => return Err(format!("Property '{}' not found", key)),
|
|
4074
4240
|
};
|
|
4075
4241
|
Ok(Value::Function(method))
|
|
@@ -4136,6 +4302,16 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4136
4302
|
"trim" => make_native_fn(move |_args: &[Value]| {
|
|
4137
4303
|
str_builtins::trim(&Value::String(s_clone.clone()))
|
|
4138
4304
|
}),
|
|
4305
|
+
"trimStart" => make_native_fn(move |_args: &[Value]| {
|
|
4306
|
+
str_builtins::trim_start(&Value::String(s_clone.clone()))
|
|
4307
|
+
}),
|
|
4308
|
+
"trimEnd" => make_native_fn(move |_args: &[Value]| {
|
|
4309
|
+
str_builtins::trim_end(&Value::String(s_clone.clone()))
|
|
4310
|
+
}),
|
|
4311
|
+
"normalize" => make_native_fn(move |args: &[Value]| {
|
|
4312
|
+
let form = args.first().cloned().unwrap_or(Value::Null);
|
|
4313
|
+
str_builtins::normalize(&Value::String(s_clone.clone()), &form)
|
|
4314
|
+
}),
|
|
4139
4315
|
"toUpperCase" => make_native_fn(move |_args: &[Value]| {
|
|
4140
4316
|
str_builtins::to_upper_case(&Value::String(s_clone.clone()))
|
|
4141
4317
|
}),
|
|
@@ -4144,11 +4320,11 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4144
4320
|
}),
|
|
4145
4321
|
"startsWith" => make_native_fn(move |args: &[Value]| {
|
|
4146
4322
|
let search = args.first().unwrap_or(&Value::Null);
|
|
4147
|
-
str_builtins::starts_with(&Value::String(s_clone.clone()), search)
|
|
4323
|
+
str_builtins::starts_with(&Value::String(s_clone.clone()), search, args.get(1))
|
|
4148
4324
|
}),
|
|
4149
4325
|
"endsWith" => make_native_fn(move |args: &[Value]| {
|
|
4150
4326
|
let search = args.first().unwrap_or(&Value::Null);
|
|
4151
|
-
str_builtins::ends_with(&Value::String(s_clone.clone()), search)
|
|
4327
|
+
str_builtins::ends_with(&Value::String(s_clone.clone()), search, args.get(1))
|
|
4152
4328
|
}),
|
|
4153
4329
|
"replace" => make_native_fn(move |args: &[Value]| {
|
|
4154
4330
|
let search = args.first().unwrap_or(&Value::Null);
|
|
@@ -4176,6 +4352,11 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4176
4352
|
tishlang_runtime::string_match_regex(&Value::String(s_clone.clone()), re)
|
|
4177
4353
|
}),
|
|
4178
4354
|
#[cfg(feature = "regex")]
|
|
4355
|
+
"matchAll" => make_native_fn(move |args: &[Value]| {
|
|
4356
|
+
let re = args.first().unwrap_or(&Value::Null);
|
|
4357
|
+
tishlang_runtime::string_match_all_regex(&Value::String(s_clone.clone()), re)
|
|
4358
|
+
}),
|
|
4359
|
+
#[cfg(feature = "regex")]
|
|
4179
4360
|
"search" => make_native_fn(move |args: &[Value]| {
|
|
4180
4361
|
let re = args.first().unwrap_or(&Value::Null);
|
|
4181
4362
|
tishlang_runtime::string_search_regex(&Value::String(s_clone.clone()), re)
|
|
@@ -4192,6 +4373,15 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4192
4373
|
let idx = args.first().unwrap_or(&Value::Null);
|
|
4193
4374
|
str_builtins::char_code_at(&Value::String(s_clone.clone()), idx)
|
|
4194
4375
|
}),
|
|
4376
|
+
"codePointAt" => make_native_fn(move |args: &[Value]| {
|
|
4377
|
+
let idx = args.first().unwrap_or(&Value::Null);
|
|
4378
|
+
str_builtins::code_point_at(&Value::String(s_clone.clone()), idx)
|
|
4379
|
+
}),
|
|
4380
|
+
"substr" => make_native_fn(move |args: &[Value]| {
|
|
4381
|
+
let start = args.first().cloned().unwrap_or(Value::Null);
|
|
4382
|
+
let length = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4383
|
+
str_builtins::substr(&Value::String(s_clone.clone()), &start, &length)
|
|
4384
|
+
}),
|
|
4195
4385
|
"repeat" => make_native_fn(move |args: &[Value]| {
|
|
4196
4386
|
let count = args.first().unwrap_or(&Value::Null);
|
|
4197
4387
|
str_builtins::repeat(&Value::String(s_clone.clone()), count)
|
|
@@ -4219,6 +4409,14 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4219
4409
|
let digits = args.first().unwrap_or(&Value::Null);
|
|
4220
4410
|
num_builtins::to_fixed(&Value::Number(n_val), digits)
|
|
4221
4411
|
}),
|
|
4412
|
+
"toExponential" => make_native_fn(move |args: &[Value]| {
|
|
4413
|
+
let digits = args.first().unwrap_or(&Value::Null);
|
|
4414
|
+
num_builtins::to_exponential(&Value::Number(n_val), digits)
|
|
4415
|
+
}),
|
|
4416
|
+
"toPrecision" => make_native_fn(move |args: &[Value]| {
|
|
4417
|
+
let precision = args.first().unwrap_or(&Value::Null);
|
|
4418
|
+
num_builtins::to_precision(&Value::Number(n_val), precision)
|
|
4419
|
+
}),
|
|
4222
4420
|
"toString" => make_native_fn(move |args: &[Value]| {
|
|
4223
4421
|
let radix = args.first().unwrap_or(&Value::Null);
|
|
4224
4422
|
num_builtins::to_string(&Value::Number(n_val), radix)
|
|
@@ -4333,12 +4531,13 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4333
4531
|
Value::NumberArray(a) => {
|
|
4334
4532
|
let i = match idx {
|
|
4335
4533
|
Value::Number(n) => *n as usize,
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
)
|
|
4341
|
-
}
|
|
4534
|
+
// A canonical integer string key ("0","1",…) indexes the array (#432); any other key
|
|
4535
|
+
// reads Null (JS reads a missing property as undefined) rather than throwing.
|
|
4536
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4537
|
+
Some(i) => i,
|
|
4538
|
+
None => return Ok(Value::Null),
|
|
4539
|
+
},
|
|
4540
|
+
_ => return Ok(Value::Null),
|
|
4342
4541
|
};
|
|
4343
4542
|
// NaN is used as the hole marker (sparse-array positions); reads return Null.
|
|
4344
4543
|
Ok(a.borrow()
|
|
@@ -4355,12 +4554,12 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4355
4554
|
Value::Array(a) => {
|
|
4356
4555
|
let i = match idx {
|
|
4357
4556
|
Value::Number(n) => *n as usize,
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4557
|
+
// Canonical integer string key → index (#432); any other key reads Null, not a throw.
|
|
4558
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4559
|
+
Some(i) => i,
|
|
4560
|
+
None => return Ok(Value::Null),
|
|
4561
|
+
},
|
|
4562
|
+
_ => return Ok(Value::Null),
|
|
4364
4563
|
};
|
|
4365
4564
|
Ok(a.borrow().get(i).cloned().unwrap_or(Value::Null))
|
|
4366
4565
|
}
|
|
@@ -4369,10 +4568,9 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4369
4568
|
Value::Number(n) => {
|
|
4370
4569
|
let n = *n;
|
|
4371
4570
|
if n < 0.0 || n.fract() != 0.0 {
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
));
|
|
4571
|
+
// A negative / non-integer string index reads `null` (JS `undefined`), NOT a
|
|
4572
|
+
// throw — interp and native already return null; the vm must agree. (#437)
|
|
4573
|
+
return Ok(Value::Null);
|
|
4376
4574
|
}
|
|
4377
4575
|
n as usize
|
|
4378
4576
|
}
|
|
@@ -4387,7 +4585,8 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4387
4585
|
// O(n) `chars().count()` pre-check (#203).
|
|
4388
4586
|
match str_builtins::nth_char(s, i) {
|
|
4389
4587
|
Some(c) => Ok(Value::String(tishlang_core::ArcStr::from(c.to_string()))),
|
|
4390
|
-
|
|
4588
|
+
// Past the end → `null` (JS `undefined`), matching interp/native, not a throw. (#437)
|
|
4589
|
+
None => Ok(Value::Null),
|
|
4391
4590
|
}
|
|
4392
4591
|
}
|
|
4393
4592
|
// A missing own property returns `null`, not a thrown error — matching dot reads
|
|
@@ -4447,6 +4646,12 @@ fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
|
|
|
4447
4646
|
Value::NumberArray(a) => {
|
|
4448
4647
|
let i = match idx {
|
|
4449
4648
|
Value::Number(n) => *n as usize,
|
|
4649
|
+
// Canonical integer string key → index (#432); a non-index string key can't be stored
|
|
4650
|
+
// in a Vec-backed array, so the write is a no-op (rather than a throw).
|
|
4651
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4652
|
+
Some(i) => i,
|
|
4653
|
+
None => return Ok(()),
|
|
4654
|
+
},
|
|
4450
4655
|
_ => {
|
|
4451
4656
|
return Err(format!(
|
|
4452
4657
|
"Array index must be number, got {}",
|
|
@@ -4487,6 +4692,11 @@ fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
|
|
|
4487
4692
|
Value::Array(a) => {
|
|
4488
4693
|
let i = match idx {
|
|
4489
4694
|
Value::Number(n) => *n as usize,
|
|
4695
|
+
// Canonical integer string key → index (#432); a non-index string key is a no-op write.
|
|
4696
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4697
|
+
Some(i) => i,
|
|
4698
|
+
None => return Ok(()),
|
|
4699
|
+
},
|
|
4490
4700
|
_ => {
|
|
4491
4701
|
return Err(format!(
|
|
4492
4702
|
"Array index must be number, got {}",
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|