@tishlang/tish-format 2.16.13 → 2.36.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/fixtures/process_capture.tish +16 -0
- package/crates/tish/tests/fs_parity.rs +103 -0
- package/crates/tish/tests/process_capture.rs +40 -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 +519 -46
- 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 +661 -27
- package/crates/tish_eval/src/natives.rs +318 -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 +236 -10
- 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 +294 -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
|
}
|
|
@@ -300,6 +337,12 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
|
|
|
300
337
|
"execFile" => Some(Value::native(|args: &[Value]| {
|
|
301
338
|
tishlang_runtime::process_exec_file(args)
|
|
302
339
|
})),
|
|
340
|
+
"execCapture" => Some(Value::native(|args: &[Value]| {
|
|
341
|
+
tishlang_runtime::process_exec_capture(args)
|
|
342
|
+
})),
|
|
343
|
+
"execFileCapture" => Some(Value::native(|args: &[Value]| {
|
|
344
|
+
tishlang_runtime::process_exec_file_capture(args)
|
|
345
|
+
})),
|
|
303
346
|
"argv" => Some(Value::Array(VmRef::new(
|
|
304
347
|
tishlang_core::process_argv()
|
|
305
348
|
.into_iter()
|
|
@@ -329,6 +372,16 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
|
|
|
329
372
|
"execFile".into(),
|
|
330
373
|
Value::native(|args: &[Value]| tishlang_runtime::process_exec_file(args)),
|
|
331
374
|
);
|
|
375
|
+
m.insert(
|
|
376
|
+
"execCapture".into(),
|
|
377
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exec_capture(args)),
|
|
378
|
+
);
|
|
379
|
+
m.insert(
|
|
380
|
+
"execFileCapture".into(),
|
|
381
|
+
Value::native(|args: &[Value]| {
|
|
382
|
+
tishlang_runtime::process_exec_file_capture(args)
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
332
385
|
m.insert(
|
|
333
386
|
"argv".into(),
|
|
334
387
|
Value::Array(VmRef::new(
|
|
@@ -537,6 +590,13 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
537
590
|
"pow".into(),
|
|
538
591
|
Value::native(|args: &[Value]| math_builtins::pow(args)),
|
|
539
592
|
);
|
|
593
|
+
// `imul` delegates to the shared builtin (ToInt32 + wrapping i32 multiply) so the vm never
|
|
594
|
+
// diverges from interp/native (#247). Was previously absent from the vm `Math` object, so
|
|
595
|
+
// `Math.imul(...)` threw "Call of non-function" on the bytecode VM.
|
|
596
|
+
math.insert(
|
|
597
|
+
"imul".into(),
|
|
598
|
+
Value::native(|args: &[Value]| math_builtins::imul(args)),
|
|
599
|
+
);
|
|
540
600
|
math.insert(
|
|
541
601
|
"sin".into(),
|
|
542
602
|
Value::native(|args: &[Value]| math_builtins::sin(args)),
|
|
@@ -597,13 +657,12 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
597
657
|
Value::Number(n.acos())
|
|
598
658
|
}),
|
|
599
659
|
);
|
|
660
|
+
// hypot delegates to the shared builtin (variadic, NaN/Infinity-correct) so the vm never diverges
|
|
661
|
+
// from interp/native (#247). The old inline `filter_map(as_number)` dropped non-number args
|
|
662
|
+
// instead of propagating NaN.
|
|
600
663
|
math.insert(
|
|
601
664
|
"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
|
-
}),
|
|
665
|
+
Value::native(|args: &[Value]| math_builtins::hypot(args)),
|
|
607
666
|
);
|
|
608
667
|
// Hyperbolic, inverse-hyperbolic, cbrt and base-2/10 logs. Like the trig block above
|
|
609
668
|
// these aren't in `math_builtins`, and on the wasm/native VM there is no host `Math`
|
|
@@ -628,6 +687,10 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
628
687
|
math_unary!("cbrt", cbrt);
|
|
629
688
|
math_unary!("log2", log2);
|
|
630
689
|
math_unary!("log10", log10);
|
|
690
|
+
math_unary!("expm1", exp_m1);
|
|
691
|
+
math_unary!("log1p", ln_1p);
|
|
692
|
+
math.insert("clz32".into(), Value::native(|a: &[Value]| math_builtins::clz32(a)));
|
|
693
|
+
math.insert("fround".into(), Value::native(|a: &[Value]| math_builtins::fround(a)));
|
|
631
694
|
math.insert("PI".into(), Value::Number(std::f64::consts::PI));
|
|
632
695
|
math.insert("E".into(), Value::Number(std::f64::consts::E));
|
|
633
696
|
g.insert("Math".into(), value_object_from_map(math));
|
|
@@ -668,6 +731,18 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
668
731
|
"decodeURI".into(),
|
|
669
732
|
Value::native(|args: &[Value]| globals_builtins::decode_uri(args)),
|
|
670
733
|
);
|
|
734
|
+
g.insert(
|
|
735
|
+
"encodeURIComponent".into(),
|
|
736
|
+
Value::native(|args: &[Value]| globals_builtins::encode_uri_component(args)),
|
|
737
|
+
);
|
|
738
|
+
g.insert(
|
|
739
|
+
"decodeURIComponent".into(),
|
|
740
|
+
Value::native(|args: &[Value]| globals_builtins::decode_uri_component(args)),
|
|
741
|
+
);
|
|
742
|
+
g.insert(
|
|
743
|
+
"structuredClone".into(),
|
|
744
|
+
Value::native(|args: &[Value]| globals_builtins::structured_clone(args)),
|
|
745
|
+
);
|
|
671
746
|
g.insert(
|
|
672
747
|
"htmlEscape".into(),
|
|
673
748
|
Value::native(|args: &[Value]| {
|
|
@@ -769,6 +844,18 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
769
844
|
"assign".into(),
|
|
770
845
|
Value::native(|args: &[Value]| globals_builtins::object_assign(args)),
|
|
771
846
|
);
|
|
847
|
+
object_methods.insert(
|
|
848
|
+
"is".into(),
|
|
849
|
+
Value::native(|args: &[Value]| globals_builtins::object_is(args)),
|
|
850
|
+
);
|
|
851
|
+
object_methods.insert(
|
|
852
|
+
"hasOwn".into(),
|
|
853
|
+
Value::native(|args: &[Value]| globals_builtins::object_has_own(args)),
|
|
854
|
+
);
|
|
855
|
+
object_methods.insert(
|
|
856
|
+
"getOwnPropertyNames".into(),
|
|
857
|
+
Value::native(|args: &[Value]| globals_builtins::object_keys(args)),
|
|
858
|
+
);
|
|
772
859
|
object_methods.insert(
|
|
773
860
|
"fromEntries".into(),
|
|
774
861
|
Value::native(|args: &[Value]| globals_builtins::object_from_entries(args)),
|
|
@@ -794,6 +881,14 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
794
881
|
"isArray".into(),
|
|
795
882
|
Value::native(|args: &[Value]| globals_builtins::array_is_array(args)),
|
|
796
883
|
);
|
|
884
|
+
array_static.insert(
|
|
885
|
+
"of".into(),
|
|
886
|
+
Value::native(|args: &[Value]| globals_builtins::array_of(args)),
|
|
887
|
+
);
|
|
888
|
+
array_static.insert(
|
|
889
|
+
"from".into(),
|
|
890
|
+
Value::native(|args: &[Value]| globals_builtins::array_from(args)),
|
|
891
|
+
);
|
|
797
892
|
array_static.insert(
|
|
798
893
|
Arc::from("__call"),
|
|
799
894
|
Value::native(|args: &[Value]| construct_builtin::array_construct(args)),
|
|
@@ -810,12 +905,26 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
810
905
|
string_static.insert(Arc::from("__call"), string_convert_fn);
|
|
811
906
|
g.insert("String".into(), value_object_from_map(string_static));
|
|
812
907
|
|
|
813
|
-
// Number(value) coercion as a callable global (issue #36).
|
|
908
|
+
// Number(value) coercion as a callable global (issue #36) + the `Number.*` statics.
|
|
814
909
|
let mut number_static = ObjectMap::default();
|
|
815
910
|
number_static.insert(
|
|
816
911
|
Arc::from("__call"),
|
|
817
912
|
Value::native(|args: &[Value]| globals_builtins::number_convert(args)),
|
|
818
913
|
);
|
|
914
|
+
number_static.insert(Arc::from("isInteger"), Value::native(|a: &[Value]| globals_builtins::number_is_integer(a)));
|
|
915
|
+
number_static.insert(Arc::from("isSafeInteger"), Value::native(|a: &[Value]| globals_builtins::number_is_safe_integer(a)));
|
|
916
|
+
number_static.insert(Arc::from("isNaN"), Value::native(|a: &[Value]| globals_builtins::number_is_nan(a)));
|
|
917
|
+
number_static.insert(Arc::from("isFinite"), Value::native(|a: &[Value]| globals_builtins::number_is_finite(a)));
|
|
918
|
+
number_static.insert(Arc::from("parseInt"), Value::native(|a: &[Value]| globals_builtins::parse_int(a)));
|
|
919
|
+
number_static.insert(Arc::from("parseFloat"), Value::native(|a: &[Value]| globals_builtins::parse_float(a)));
|
|
920
|
+
number_static.insert(Arc::from("MAX_SAFE_INTEGER"), Value::Number(9_007_199_254_740_991.0));
|
|
921
|
+
number_static.insert(Arc::from("MIN_SAFE_INTEGER"), Value::Number(-9_007_199_254_740_991.0));
|
|
922
|
+
number_static.insert(Arc::from("EPSILON"), Value::Number(f64::EPSILON));
|
|
923
|
+
number_static.insert(Arc::from("MAX_VALUE"), Value::Number(f64::MAX));
|
|
924
|
+
number_static.insert(Arc::from("MIN_VALUE"), Value::Number(5e-324));
|
|
925
|
+
number_static.insert(Arc::from("POSITIVE_INFINITY"), Value::Number(f64::INFINITY));
|
|
926
|
+
number_static.insert(Arc::from("NEGATIVE_INFINITY"), Value::Number(f64::NEG_INFINITY));
|
|
927
|
+
number_static.insert(Arc::from("NaN"), Value::Number(f64::NAN));
|
|
819
928
|
g.insert("Number".into(), value_object_from_map(number_static));
|
|
820
929
|
|
|
821
930
|
// JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
|
|
@@ -866,6 +975,14 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
866
975
|
"execFile".into(),
|
|
867
976
|
Value::native(|args: &[Value]| tishlang_runtime::process_exec_file(args)),
|
|
868
977
|
);
|
|
978
|
+
process_obj.insert(
|
|
979
|
+
"execCapture".into(),
|
|
980
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exec_capture(args)),
|
|
981
|
+
);
|
|
982
|
+
process_obj.insert(
|
|
983
|
+
"execFileCapture".into(),
|
|
984
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exec_file_capture(args)),
|
|
985
|
+
);
|
|
869
986
|
process_obj.insert(
|
|
870
987
|
"argv".into(),
|
|
871
988
|
Value::Array(VmRef::new(
|
|
@@ -1383,7 +1500,7 @@ mod hof_fusion {
|
|
|
1383
1500
|
/// (which would pass a `Value::Bool`, not a number). No-initial-value semantics match the generic
|
|
1384
1501
|
/// path: absent init (`Value::Null`) with a non-empty array seeds `acc = data[0]` and scans from
|
|
1385
1502
|
/// 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,
|
|
1503
|
+
pub(super) fn reduce(arr: &Value, cb: &Value, initial: Option<&Value>) -> Option<Value> {
|
|
1387
1504
|
let nf = fused_numeric_fn(cb)?;
|
|
1388
1505
|
if nf.arity() > 3 || nf.result_is_bool() {
|
|
1389
1506
|
return None;
|
|
@@ -1392,11 +1509,11 @@ mod hof_fusion {
|
|
|
1392
1509
|
let arity = nf.arity();
|
|
1393
1510
|
// Determine seed. Reduce with no initial value AND an empty array is a TypeError in JS — let
|
|
1394
1511
|
// 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
|
|
1512
|
+
let (start, mut acc) = match initial {
|
|
1513
|
+
None if !data.is_empty() => (1usize, data[0]),
|
|
1514
|
+
None => return None, // empty + no init → generic path throws
|
|
1515
|
+
Some(Value::Number(n)) => (0usize, *n),
|
|
1516
|
+
Some(_) => return None, // non-numeric/explicit-null init → generic path (acc wouldn't stay f64)
|
|
1400
1517
|
};
|
|
1401
1518
|
let mut args = [0.0f64; 3];
|
|
1402
1519
|
for (i, &x) in data.iter().enumerate().skip(start) {
|
|
@@ -3823,13 +3940,21 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3823
3940
|
}),
|
|
3824
3941
|
"reduce" => make_native_fn(move |args| {
|
|
3825
3942
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3826
|
-
let init = args.get(1).cloned()
|
|
3943
|
+
let init = args.get(1).cloned(); // None = no initial value (empty array → throw)
|
|
3827
3944
|
#[cfg(not(target_arch = "wasm32"))]
|
|
3828
|
-
if let Some(v) = hof_fusion::reduce(&bv, &cb,
|
|
3945
|
+
if let Some(v) = hof_fusion::reduce(&bv, &cb, init.as_ref()) {
|
|
3829
3946
|
return v;
|
|
3830
3947
|
}
|
|
3831
|
-
arr_builtins::reduce(&bv, &cb,
|
|
3948
|
+
arr_builtins::reduce(&bv, &cb, init.as_ref())
|
|
3832
3949
|
}),
|
|
3950
|
+
"reduceRight" => make_native_fn(move |args| {
|
|
3951
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3952
|
+
let init = args.get(1).cloned();
|
|
3953
|
+
arr_builtins::reduce_right(&bv, &cb, init.as_ref())
|
|
3954
|
+
}),
|
|
3955
|
+
"keys" => make_native_fn(move |_| arr_builtins::keys(&bv)),
|
|
3956
|
+
"values" => make_native_fn(move |_| arr_builtins::values(&bv)),
|
|
3957
|
+
"entries" => make_native_fn(move |_| arr_builtins::entries(&bv)),
|
|
3833
3958
|
"forEach" => make_native_fn(move |args| {
|
|
3834
3959
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3835
3960
|
#[cfg(not(target_arch = "wasm32"))]
|
|
@@ -3879,6 +4004,21 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3879
4004
|
arr_builtins::flat_map(&bv, &cb)
|
|
3880
4005
|
}),
|
|
3881
4006
|
"reverse" => make_native_fn(move |_| arr_builtins::reverse(&bv)),
|
|
4007
|
+
"toReversed" => make_native_fn(move |_| arr_builtins::to_reversed(&bv)),
|
|
4008
|
+
"toSorted" => {
|
|
4009
|
+
make_native_fn(move |args| arr_builtins::to_sorted(&bv, args.first()))
|
|
4010
|
+
}
|
|
4011
|
+
"with" => make_native_fn(move |args| {
|
|
4012
|
+
let i = args.first().cloned().unwrap_or(Value::Null);
|
|
4013
|
+
let v = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4014
|
+
arr_builtins::with(&bv, &i, &v)
|
|
4015
|
+
}),
|
|
4016
|
+
"toSpliced" => make_native_fn(move |args| {
|
|
4017
|
+
let start = args.first().cloned().unwrap_or(Value::Null);
|
|
4018
|
+
let dc = args.get(1).cloned();
|
|
4019
|
+
let items: Vec<Value> = args.iter().skip(2).cloned().collect();
|
|
4020
|
+
arr_builtins::to_spliced(&bv, &start, dc.as_ref(), &items)
|
|
4021
|
+
}),
|
|
3882
4022
|
"fill" => make_native_fn(move |args| {
|
|
3883
4023
|
let v = args.first().cloned().unwrap_or(Value::Null);
|
|
3884
4024
|
let s = args.get(1).cloned().unwrap_or(Value::Null);
|
|
@@ -3893,7 +4033,17 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3893
4033
|
"concat" => make_native_fn(move |args| arr_builtins::concat(&bv, args)),
|
|
3894
4034
|
"indexOf" => make_native_fn(move |args| {
|
|
3895
4035
|
let s = args.first().cloned().unwrap_or(Value::Null);
|
|
3896
|
-
arr_builtins::index_of(&bv, &s)
|
|
4036
|
+
arr_builtins::index_of(&bv, &s, args.get(1))
|
|
4037
|
+
}),
|
|
4038
|
+
"lastIndexOf" => make_native_fn(move |args| {
|
|
4039
|
+
let s = args.first().cloned().unwrap_or(Value::Null);
|
|
4040
|
+
arr_builtins::last_index_of(&bv, &s, args.get(1))
|
|
4041
|
+
}),
|
|
4042
|
+
"copyWithin" => make_native_fn(move |args| {
|
|
4043
|
+
let t = args.first().cloned().unwrap_or(Value::Null);
|
|
4044
|
+
let s = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4045
|
+
let e = args.get(2).cloned().unwrap_or(Value::Null);
|
|
4046
|
+
arr_builtins::copy_within(&bv, &t, &s, &e)
|
|
3897
4047
|
}),
|
|
3898
4048
|
"includes" => make_native_fn(move |args| {
|
|
3899
4049
|
let s = args.first().cloned().unwrap_or(Value::Null);
|
|
@@ -3966,7 +4116,17 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3966
4116
|
}),
|
|
3967
4117
|
"indexOf" => make_native_fn(move |args: &[Value]| {
|
|
3968
4118
|
let search = args.first().unwrap_or(&Value::Null);
|
|
3969
|
-
arr_builtins::index_of(&Value::Array(a_clone.clone()), search)
|
|
4119
|
+
arr_builtins::index_of(&Value::Array(a_clone.clone()), search, args.get(1))
|
|
4120
|
+
}),
|
|
4121
|
+
"lastIndexOf" => make_native_fn(move |args: &[Value]| {
|
|
4122
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
4123
|
+
arr_builtins::last_index_of(&Value::Array(a_clone.clone()), search, args.get(1))
|
|
4124
|
+
}),
|
|
4125
|
+
"copyWithin" => make_native_fn(move |args: &[Value]| {
|
|
4126
|
+
let t = args.first().cloned().unwrap_or(Value::Null);
|
|
4127
|
+
let s = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4128
|
+
let e = args.get(2).cloned().unwrap_or(Value::Null);
|
|
4129
|
+
arr_builtins::copy_within(&Value::Array(a_clone.clone()), &t, &s, &e)
|
|
3970
4130
|
}),
|
|
3971
4131
|
"includes" => make_native_fn(move |args: &[Value]| {
|
|
3972
4132
|
let search = args.first().unwrap_or(&Value::Null);
|
|
@@ -3995,14 +4155,22 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
3995
4155
|
}),
|
|
3996
4156
|
"reduce" => make_native_fn(move |args: &[Value]| {
|
|
3997
4157
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
3998
|
-
let init = args.get(1).cloned()
|
|
4158
|
+
let init = args.get(1).cloned(); // None = no initial value (empty array → throw)
|
|
3999
4159
|
let arr = Value::Array(a_clone.clone());
|
|
4000
4160
|
#[cfg(not(target_arch = "wasm32"))]
|
|
4001
|
-
if let Some(v) = hof_fusion::reduce(&arr, &cb,
|
|
4161
|
+
if let Some(v) = hof_fusion::reduce(&arr, &cb, init.as_ref()) {
|
|
4002
4162
|
return v;
|
|
4003
4163
|
}
|
|
4004
|
-
arr_builtins::reduce(&arr, &cb,
|
|
4164
|
+
arr_builtins::reduce(&arr, &cb, init.as_ref())
|
|
4005
4165
|
}),
|
|
4166
|
+
"reduceRight" => make_native_fn(move |args: &[Value]| {
|
|
4167
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
4168
|
+
let init = args.get(1).cloned();
|
|
4169
|
+
arr_builtins::reduce_right(&Value::Array(a_clone.clone()), &cb, init.as_ref())
|
|
4170
|
+
}),
|
|
4171
|
+
"keys" => make_native_fn(move |_: &[Value]| arr_builtins::keys(&Value::Array(a_clone.clone()))),
|
|
4172
|
+
"values" => make_native_fn(move |_: &[Value]| arr_builtins::values(&Value::Array(a_clone.clone()))),
|
|
4173
|
+
"entries" => make_native_fn(move |_: &[Value]| arr_builtins::entries(&Value::Array(a_clone.clone()))),
|
|
4006
4174
|
"forEach" => make_native_fn(move |args: &[Value]| {
|
|
4007
4175
|
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
4008
4176
|
let arr = Value::Array(a_clone.clone());
|
|
@@ -4070,6 +4238,28 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4070
4238
|
&items,
|
|
4071
4239
|
)
|
|
4072
4240
|
}),
|
|
4241
|
+
"toReversed" => make_native_fn(move |_: &[Value]| {
|
|
4242
|
+
arr_builtins::to_reversed(&Value::Array(a_clone.clone()))
|
|
4243
|
+
}),
|
|
4244
|
+
"toSorted" => make_native_fn(move |args: &[Value]| {
|
|
4245
|
+
arr_builtins::to_sorted(&Value::Array(a_clone.clone()), args.first())
|
|
4246
|
+
}),
|
|
4247
|
+
"with" => make_native_fn(move |args: &[Value]| {
|
|
4248
|
+
let i = args.first().cloned().unwrap_or(Value::Null);
|
|
4249
|
+
let v = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4250
|
+
arr_builtins::with(&Value::Array(a_clone.clone()), &i, &v)
|
|
4251
|
+
}),
|
|
4252
|
+
"toSpliced" => make_native_fn(move |args: &[Value]| {
|
|
4253
|
+
let start = args.first().unwrap_or(&Value::Null);
|
|
4254
|
+
let delete_count = args.get(1).map(|v| v as &Value);
|
|
4255
|
+
let items: Vec<Value> = args.get(2..).unwrap_or(&[]).to_vec();
|
|
4256
|
+
arr_builtins::to_spliced(
|
|
4257
|
+
&Value::Array(a_clone.clone()),
|
|
4258
|
+
start,
|
|
4259
|
+
delete_count,
|
|
4260
|
+
&items,
|
|
4261
|
+
)
|
|
4262
|
+
}),
|
|
4073
4263
|
_ => return Err(format!("Property '{}' not found", key)),
|
|
4074
4264
|
};
|
|
4075
4265
|
Ok(Value::Function(method))
|
|
@@ -4136,6 +4326,16 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4136
4326
|
"trim" => make_native_fn(move |_args: &[Value]| {
|
|
4137
4327
|
str_builtins::trim(&Value::String(s_clone.clone()))
|
|
4138
4328
|
}),
|
|
4329
|
+
"trimStart" => make_native_fn(move |_args: &[Value]| {
|
|
4330
|
+
str_builtins::trim_start(&Value::String(s_clone.clone()))
|
|
4331
|
+
}),
|
|
4332
|
+
"trimEnd" => make_native_fn(move |_args: &[Value]| {
|
|
4333
|
+
str_builtins::trim_end(&Value::String(s_clone.clone()))
|
|
4334
|
+
}),
|
|
4335
|
+
"normalize" => make_native_fn(move |args: &[Value]| {
|
|
4336
|
+
let form = args.first().cloned().unwrap_or(Value::Null);
|
|
4337
|
+
str_builtins::normalize(&Value::String(s_clone.clone()), &form)
|
|
4338
|
+
}),
|
|
4139
4339
|
"toUpperCase" => make_native_fn(move |_args: &[Value]| {
|
|
4140
4340
|
str_builtins::to_upper_case(&Value::String(s_clone.clone()))
|
|
4141
4341
|
}),
|
|
@@ -4144,11 +4344,11 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4144
4344
|
}),
|
|
4145
4345
|
"startsWith" => make_native_fn(move |args: &[Value]| {
|
|
4146
4346
|
let search = args.first().unwrap_or(&Value::Null);
|
|
4147
|
-
str_builtins::starts_with(&Value::String(s_clone.clone()), search)
|
|
4347
|
+
str_builtins::starts_with(&Value::String(s_clone.clone()), search, args.get(1))
|
|
4148
4348
|
}),
|
|
4149
4349
|
"endsWith" => make_native_fn(move |args: &[Value]| {
|
|
4150
4350
|
let search = args.first().unwrap_or(&Value::Null);
|
|
4151
|
-
str_builtins::ends_with(&Value::String(s_clone.clone()), search)
|
|
4351
|
+
str_builtins::ends_with(&Value::String(s_clone.clone()), search, args.get(1))
|
|
4152
4352
|
}),
|
|
4153
4353
|
"replace" => make_native_fn(move |args: &[Value]| {
|
|
4154
4354
|
let search = args.first().unwrap_or(&Value::Null);
|
|
@@ -4176,6 +4376,11 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4176
4376
|
tishlang_runtime::string_match_regex(&Value::String(s_clone.clone()), re)
|
|
4177
4377
|
}),
|
|
4178
4378
|
#[cfg(feature = "regex")]
|
|
4379
|
+
"matchAll" => make_native_fn(move |args: &[Value]| {
|
|
4380
|
+
let re = args.first().unwrap_or(&Value::Null);
|
|
4381
|
+
tishlang_runtime::string_match_all_regex(&Value::String(s_clone.clone()), re)
|
|
4382
|
+
}),
|
|
4383
|
+
#[cfg(feature = "regex")]
|
|
4179
4384
|
"search" => make_native_fn(move |args: &[Value]| {
|
|
4180
4385
|
let re = args.first().unwrap_or(&Value::Null);
|
|
4181
4386
|
tishlang_runtime::string_search_regex(&Value::String(s_clone.clone()), re)
|
|
@@ -4192,6 +4397,15 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4192
4397
|
let idx = args.first().unwrap_or(&Value::Null);
|
|
4193
4398
|
str_builtins::char_code_at(&Value::String(s_clone.clone()), idx)
|
|
4194
4399
|
}),
|
|
4400
|
+
"codePointAt" => make_native_fn(move |args: &[Value]| {
|
|
4401
|
+
let idx = args.first().unwrap_or(&Value::Null);
|
|
4402
|
+
str_builtins::code_point_at(&Value::String(s_clone.clone()), idx)
|
|
4403
|
+
}),
|
|
4404
|
+
"substr" => make_native_fn(move |args: &[Value]| {
|
|
4405
|
+
let start = args.first().cloned().unwrap_or(Value::Null);
|
|
4406
|
+
let length = args.get(1).cloned().unwrap_or(Value::Null);
|
|
4407
|
+
str_builtins::substr(&Value::String(s_clone.clone()), &start, &length)
|
|
4408
|
+
}),
|
|
4195
4409
|
"repeat" => make_native_fn(move |args: &[Value]| {
|
|
4196
4410
|
let count = args.first().unwrap_or(&Value::Null);
|
|
4197
4411
|
str_builtins::repeat(&Value::String(s_clone.clone()), count)
|
|
@@ -4219,6 +4433,14 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
|
4219
4433
|
let digits = args.first().unwrap_or(&Value::Null);
|
|
4220
4434
|
num_builtins::to_fixed(&Value::Number(n_val), digits)
|
|
4221
4435
|
}),
|
|
4436
|
+
"toExponential" => make_native_fn(move |args: &[Value]| {
|
|
4437
|
+
let digits = args.first().unwrap_or(&Value::Null);
|
|
4438
|
+
num_builtins::to_exponential(&Value::Number(n_val), digits)
|
|
4439
|
+
}),
|
|
4440
|
+
"toPrecision" => make_native_fn(move |args: &[Value]| {
|
|
4441
|
+
let precision = args.first().unwrap_or(&Value::Null);
|
|
4442
|
+
num_builtins::to_precision(&Value::Number(n_val), precision)
|
|
4443
|
+
}),
|
|
4222
4444
|
"toString" => make_native_fn(move |args: &[Value]| {
|
|
4223
4445
|
let radix = args.first().unwrap_or(&Value::Null);
|
|
4224
4446
|
num_builtins::to_string(&Value::Number(n_val), radix)
|
|
@@ -4333,12 +4555,13 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4333
4555
|
Value::NumberArray(a) => {
|
|
4334
4556
|
let i = match idx {
|
|
4335
4557
|
Value::Number(n) => *n as usize,
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
)
|
|
4341
|
-
}
|
|
4558
|
+
// A canonical integer string key ("0","1",…) indexes the array (#432); any other key
|
|
4559
|
+
// reads Null (JS reads a missing property as undefined) rather than throwing.
|
|
4560
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4561
|
+
Some(i) => i,
|
|
4562
|
+
None => return Ok(Value::Null),
|
|
4563
|
+
},
|
|
4564
|
+
_ => return Ok(Value::Null),
|
|
4342
4565
|
};
|
|
4343
4566
|
// NaN is used as the hole marker (sparse-array positions); reads return Null.
|
|
4344
4567
|
Ok(a.borrow()
|
|
@@ -4355,12 +4578,12 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4355
4578
|
Value::Array(a) => {
|
|
4356
4579
|
let i = match idx {
|
|
4357
4580
|
Value::Number(n) => *n as usize,
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4581
|
+
// Canonical integer string key → index (#432); any other key reads Null, not a throw.
|
|
4582
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4583
|
+
Some(i) => i,
|
|
4584
|
+
None => return Ok(Value::Null),
|
|
4585
|
+
},
|
|
4586
|
+
_ => return Ok(Value::Null),
|
|
4364
4587
|
};
|
|
4365
4588
|
Ok(a.borrow().get(i).cloned().unwrap_or(Value::Null))
|
|
4366
4589
|
}
|
|
@@ -4369,10 +4592,9 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4369
4592
|
Value::Number(n) => {
|
|
4370
4593
|
let n = *n;
|
|
4371
4594
|
if n < 0.0 || n.fract() != 0.0 {
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
));
|
|
4595
|
+
// A negative / non-integer string index reads `null` (JS `undefined`), NOT a
|
|
4596
|
+
// throw — interp and native already return null; the vm must agree. (#437)
|
|
4597
|
+
return Ok(Value::Null);
|
|
4376
4598
|
}
|
|
4377
4599
|
n as usize
|
|
4378
4600
|
}
|
|
@@ -4387,7 +4609,8 @@ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
|
4387
4609
|
// O(n) `chars().count()` pre-check (#203).
|
|
4388
4610
|
match str_builtins::nth_char(s, i) {
|
|
4389
4611
|
Some(c) => Ok(Value::String(tishlang_core::ArcStr::from(c.to_string()))),
|
|
4390
|
-
|
|
4612
|
+
// Past the end → `null` (JS `undefined`), matching interp/native, not a throw. (#437)
|
|
4613
|
+
None => Ok(Value::Null),
|
|
4391
4614
|
}
|
|
4392
4615
|
}
|
|
4393
4616
|
// A missing own property returns `null`, not a thrown error — matching dot reads
|
|
@@ -4447,6 +4670,12 @@ fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
|
|
|
4447
4670
|
Value::NumberArray(a) => {
|
|
4448
4671
|
let i = match idx {
|
|
4449
4672
|
Value::Number(n) => *n as usize,
|
|
4673
|
+
// Canonical integer string key → index (#432); a non-index string key can't be stored
|
|
4674
|
+
// in a Vec-backed array, so the write is a no-op (rather than a throw).
|
|
4675
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4676
|
+
Some(i) => i,
|
|
4677
|
+
None => return Ok(()),
|
|
4678
|
+
},
|
|
4450
4679
|
_ => {
|
|
4451
4680
|
return Err(format!(
|
|
4452
4681
|
"Array index must be number, got {}",
|
|
@@ -4487,6 +4716,11 @@ fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
|
|
|
4487
4716
|
Value::Array(a) => {
|
|
4488
4717
|
let i = match idx {
|
|
4489
4718
|
Value::Number(n) => *n as usize,
|
|
4719
|
+
// Canonical integer string key → index (#432); a non-index string key is a no-op write.
|
|
4720
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
4721
|
+
Some(i) => i,
|
|
4722
|
+
None => return Ok(()),
|
|
4723
|
+
},
|
|
4490
4724
|
_ => {
|
|
4491
4725
|
return Err(format!(
|
|
4492
4726
|
"Array index must be number, got {}",
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|