@tishlang/tish-lsp 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-lsp +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/src/lib.rs +23 -4
- package/crates/tish_lsp/Cargo.toml +1 -1
- 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-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -118,18 +118,23 @@ pub use tishlang_builtins::array::{
|
|
|
118
118
|
find_last_index as array_find_last_index, flat as array_flat_impl, flat_map as array_flat_map,
|
|
119
119
|
for_each as array_for_each, includes as array_includes_impl, index_of as array_index_of_impl,
|
|
120
120
|
join as array_join_impl, map as array_map, pop as array_pop, push as array_push_impl,
|
|
121
|
-
fill as array_fill,
|
|
121
|
+
fill as array_fill, last_index_of as array_last_index_of, copy_within as array_copy_within,
|
|
122
|
+
reduce as array_reduce, reduce_right as array_reduce_right, reverse as array_reverse,
|
|
123
|
+
keys as array_keys, values as array_values, entries as array_entries, shift as array_shift,
|
|
122
124
|
shuffle as array_shuffle, slice as array_slice_impl, snapshot_values as array_snapshot_values,
|
|
123
125
|
as_f64_snapshot as array_as_f64_snapshot, some as array_some,
|
|
124
126
|
sort_by_keys as array_sort_by_keys, sort_default as array_sort_default,
|
|
125
127
|
sort_numeric_asc as array_sort_numeric_asc, sort_numeric_desc as array_sort_numeric_desc,
|
|
126
128
|
sort_with_comparator as array_sort_with_comparator, splice as array_splice_impl,
|
|
129
|
+
to_reversed as array_to_reversed, to_sorted as array_to_sorted,
|
|
130
|
+
to_spliced as array_to_spliced, with as array_with,
|
|
127
131
|
unshift as array_unshift_impl,
|
|
128
132
|
};
|
|
129
133
|
|
|
130
134
|
// Re-export string methods from tishlang_builtins
|
|
131
135
|
pub use tishlang_builtins::string::{
|
|
132
136
|
at as string_at_impl, char_at as string_char_at_impl, char_code_at as string_char_code_at_impl,
|
|
137
|
+
code_point_at as string_code_point_at,
|
|
133
138
|
ends_with as string_ends_with_impl, escape_html as string_escape_html_impl,
|
|
134
139
|
includes as string_includes_impl, index_of as string_index_of_impl,
|
|
135
140
|
last_index_of as string_last_index_of_impl, pad_end as string_pad_end_impl,
|
|
@@ -138,8 +143,9 @@ pub use tishlang_builtins::string::{
|
|
|
138
143
|
slice as string_slice_impl, split as string_split_impl, split_limit as string_split_limit_impl,
|
|
139
144
|
starts_with as string_starts_with_impl, substr as string_substr_impl,
|
|
140
145
|
substring as string_substring_impl,
|
|
141
|
-
|
|
142
|
-
trim as string_trim,
|
|
146
|
+
normalize as string_normalize, to_lower_case as string_to_lower_case,
|
|
147
|
+
to_upper_case as string_to_upper_case, trim as string_trim, trim_end as string_trim_end,
|
|
148
|
+
trim_start as string_trim_start,
|
|
143
149
|
};
|
|
144
150
|
|
|
145
151
|
// Wrapper functions to maintain API compatibility
|
|
@@ -149,8 +155,8 @@ pub fn array_push(arr: &Value, args: &[Value]) -> Value {
|
|
|
149
155
|
pub fn array_unshift(arr: &Value, args: &[Value]) -> Value {
|
|
150
156
|
array_unshift_impl(arr, args)
|
|
151
157
|
}
|
|
152
|
-
pub fn array_index_of(arr: &Value, search: &Value) -> Value {
|
|
153
|
-
array_index_of_impl(arr, search)
|
|
158
|
+
pub fn array_index_of(arr: &Value, search: &Value, from: Option<&Value>) -> Value {
|
|
159
|
+
array_index_of_impl(arr, search, from)
|
|
154
160
|
}
|
|
155
161
|
pub fn array_includes(arr: &Value, search: &Value, from: &Value) -> Value {
|
|
156
162
|
array_includes_impl(arr, search, Some(from))
|
|
@@ -217,11 +223,11 @@ pub fn string_split_limit(s: &Value, sep: &Value, limit: &Value) -> Value {
|
|
|
217
223
|
}
|
|
218
224
|
string_split_limit_impl(s, sep, max)
|
|
219
225
|
}
|
|
220
|
-
pub fn string_starts_with(s: &Value, search: &Value) -> Value {
|
|
221
|
-
string_starts_with_impl(s, search)
|
|
226
|
+
pub fn string_starts_with(s: &Value, search: &Value, position: Option<&Value>) -> Value {
|
|
227
|
+
string_starts_with_impl(s, search, position)
|
|
222
228
|
}
|
|
223
|
-
pub fn string_ends_with(s: &Value, search: &Value) -> Value {
|
|
224
|
-
string_ends_with_impl(s, search)
|
|
229
|
+
pub fn string_ends_with(s: &Value, search: &Value, end_position: Option<&Value>) -> Value {
|
|
230
|
+
string_ends_with_impl(s, search, end_position)
|
|
225
231
|
}
|
|
226
232
|
pub fn string_replace(s: &Value, search: &Value, replacement: &Value) -> Value {
|
|
227
233
|
#[cfg(feature = "regex")]
|
|
@@ -391,6 +397,12 @@ pub fn string_last_index_of(s: &Value, search: &Value, position: &Value) -> Valu
|
|
|
391
397
|
pub fn number_to_fixed(n: &Value, digits: &Value) -> Value {
|
|
392
398
|
tishlang_builtins::number::to_fixed(n, digits)
|
|
393
399
|
}
|
|
400
|
+
pub fn number_to_exponential(n: &Value, digits: &Value) -> Value {
|
|
401
|
+
tishlang_builtins::number::to_exponential(n, digits)
|
|
402
|
+
}
|
|
403
|
+
pub fn number_to_precision(n: &Value, precision: &Value) -> Value {
|
|
404
|
+
tishlang_builtins::number::to_precision(n, precision)
|
|
405
|
+
}
|
|
394
406
|
|
|
395
407
|
/// `.toString([radix])` for the compiled backend (issue #59). A number receiver uses the
|
|
396
408
|
/// shared radix formatter so it stays byte-identical with the VM / interpreter; any other
|
|
@@ -800,6 +812,15 @@ pub fn decode_uri(args: &[Value]) -> Value {
|
|
|
800
812
|
pub fn encode_uri(args: &[Value]) -> Value {
|
|
801
813
|
builtins_encode_uri(args)
|
|
802
814
|
}
|
|
815
|
+
pub fn encode_uri_component(args: &[Value]) -> Value {
|
|
816
|
+
tishlang_builtins::globals::encode_uri_component(args)
|
|
817
|
+
}
|
|
818
|
+
pub fn decode_uri_component(args: &[Value]) -> Value {
|
|
819
|
+
tishlang_builtins::globals::decode_uri_component(args)
|
|
820
|
+
}
|
|
821
|
+
pub fn structured_clone(args: &[Value]) -> Value {
|
|
822
|
+
tishlang_builtins::globals::structured_clone(args)
|
|
823
|
+
}
|
|
803
824
|
|
|
804
825
|
// Math functions - use tishlang_builtins::math
|
|
805
826
|
pub use tishlang_builtins::math::{
|
|
@@ -903,6 +924,14 @@ runtime_math_unary!(math_atanh, atanh);
|
|
|
903
924
|
runtime_math_unary!(math_cbrt, cbrt);
|
|
904
925
|
runtime_math_unary!(math_log2, log2);
|
|
905
926
|
runtime_math_unary!(math_log10, log10);
|
|
927
|
+
runtime_math_unary!(math_expm1, exp_m1);
|
|
928
|
+
runtime_math_unary!(math_log1p, ln_1p);
|
|
929
|
+
pub fn math_clz32(args: &[Value]) -> Value {
|
|
930
|
+
tishlang_builtins::math::clz32(args)
|
|
931
|
+
}
|
|
932
|
+
pub fn math_fround(args: &[Value]) -> Value {
|
|
933
|
+
tishlang_builtins::math::fround(args)
|
|
934
|
+
}
|
|
906
935
|
|
|
907
936
|
pub fn json_stringify(args: &[Value]) -> Value {
|
|
908
937
|
let v = args.first().cloned().unwrap_or(Value::Null);
|
|
@@ -921,6 +950,18 @@ pub fn json_parse(args: &[Value]) -> Value {
|
|
|
921
950
|
pub fn array_is_array(args: &[Value]) -> Value {
|
|
922
951
|
builtins_array_is_array(args)
|
|
923
952
|
}
|
|
953
|
+
pub fn array_of(args: &[Value]) -> Value {
|
|
954
|
+
tishlang_builtins::globals::array_of(args)
|
|
955
|
+
}
|
|
956
|
+
pub fn array_from(args: &[Value]) -> Value {
|
|
957
|
+
tishlang_builtins::globals::array_from(args)
|
|
958
|
+
}
|
|
959
|
+
pub fn object_is(args: &[Value]) -> Value {
|
|
960
|
+
tishlang_builtins::globals::object_is(args)
|
|
961
|
+
}
|
|
962
|
+
pub fn object_has_own(args: &[Value]) -> Value {
|
|
963
|
+
tishlang_builtins::globals::object_has_own(args)
|
|
964
|
+
}
|
|
924
965
|
|
|
925
966
|
pub fn string_from_char_code(args: &[Value]) -> Value {
|
|
926
967
|
builtins_string_from_char_code(args)
|
|
@@ -935,6 +976,18 @@ pub fn string_convert(args: &[Value]) -> Value {
|
|
|
935
976
|
pub fn number_convert(args: &[Value]) -> Value {
|
|
936
977
|
builtins_number_convert(args)
|
|
937
978
|
}
|
|
979
|
+
pub fn number_is_integer(args: &[Value]) -> Value {
|
|
980
|
+
tishlang_builtins::globals::number_is_integer(args)
|
|
981
|
+
}
|
|
982
|
+
pub fn number_is_safe_integer(args: &[Value]) -> Value {
|
|
983
|
+
tishlang_builtins::globals::number_is_safe_integer(args)
|
|
984
|
+
}
|
|
985
|
+
pub fn number_is_nan(args: &[Value]) -> Value {
|
|
986
|
+
tishlang_builtins::globals::number_is_nan(args)
|
|
987
|
+
}
|
|
988
|
+
pub fn number_is_finite(args: &[Value]) -> Value {
|
|
989
|
+
tishlang_builtins::globals::number_is_finite(args)
|
|
990
|
+
}
|
|
938
991
|
|
|
939
992
|
#[cfg(feature = "process")]
|
|
940
993
|
pub fn process_exit(args: &[Value]) -> Value {
|
|
@@ -996,9 +1049,75 @@ pub fn process_exec_file(args: &[Value]) -> Value {
|
|
|
996
1049
|
}
|
|
997
1050
|
}
|
|
998
1051
|
|
|
1052
|
+
/// Build the `{ code: number, stdout: string, stderr: string }` capture object (#475). stdout/stderr
|
|
1053
|
+
/// are lossy-UTF-8 decoded, matching the `fs` string convention (invalid bytes → replacement chars,
|
|
1054
|
+
/// never dropped output).
|
|
1055
|
+
#[cfg(feature = "process")]
|
|
1056
|
+
fn process_capture_obj(code: i32, stdout: String, stderr: String) -> Value {
|
|
1057
|
+
let mut m = tishlang_core::ObjectMap::default();
|
|
1058
|
+
m.insert("code".into(), Value::Number(code as f64));
|
|
1059
|
+
m.insert("stdout".into(), Value::String(stdout.into()));
|
|
1060
|
+
m.insert("stderr".into(), Value::String(stderr.into()));
|
|
1061
|
+
Value::object(m)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/// Build the capture object from a `Command::output()` result. `code` is the exit status, or `-1`
|
|
1065
|
+
/// when the process was killed by a signal or failed to spawn (spawn failure puts the OS error in
|
|
1066
|
+
/// `stderr`).
|
|
1067
|
+
#[cfg(feature = "process")]
|
|
1068
|
+
fn process_capture_result(out: std::io::Result<std::process::Output>) -> Value {
|
|
1069
|
+
match out {
|
|
1070
|
+
Ok(o) => process_capture_obj(
|
|
1071
|
+
o.status.code().unwrap_or(-1),
|
|
1072
|
+
String::from_utf8_lossy(&o.stdout).into_owned(),
|
|
1073
|
+
String::from_utf8_lossy(&o.stderr).into_owned(),
|
|
1074
|
+
),
|
|
1075
|
+
Err(e) => process_capture_obj(-1, String::new(), e.to_string()),
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/// `process.execCapture(cmd)` — run `sh -c cmd` and CAPTURE its output as
|
|
1080
|
+
/// `{ code: number, stdout: string, stderr: string }` (Rust `Command::output`; Node
|
|
1081
|
+
/// `execSync`/`spawnSync({ encoding })`). Use `execFileCapture` for untrusted arguments — this shell
|
|
1082
|
+
/// form interprets metacharacters.
|
|
1083
|
+
#[cfg(feature = "process")]
|
|
1084
|
+
pub fn process_exec_capture(args: &[Value]) -> Value {
|
|
1085
|
+
use std::process::Command;
|
|
1086
|
+
let cmd = args
|
|
1087
|
+
.first()
|
|
1088
|
+
.map(|v| v.to_display_string())
|
|
1089
|
+
.unwrap_or_default();
|
|
1090
|
+
if cmd.is_empty() {
|
|
1091
|
+
return process_capture_obj(0, String::new(), String::new());
|
|
1092
|
+
}
|
|
1093
|
+
process_capture_result(Command::new("sh").arg("-c").arg(&cmd).output())
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/// `process.execFileCapture(program, [args])` — run a program directly, WITHOUT a shell, capturing
|
|
1097
|
+
/// output as `{ code, stdout, stderr }`. Arguments are passed verbatim, so shell metacharacters in
|
|
1098
|
+
/// untrusted data are never interpreted — the safe capturing counterpart for input-derived args
|
|
1099
|
+
/// (#384, #475). This is the primitive for shelling out to a CLI and reading its output (e.g.
|
|
1100
|
+
/// `git status --porcelain`).
|
|
1101
|
+
#[cfg(feature = "process")]
|
|
1102
|
+
pub fn process_exec_file_capture(args: &[Value]) -> Value {
|
|
1103
|
+
use std::process::Command;
|
|
1104
|
+
let program = args
|
|
1105
|
+
.first()
|
|
1106
|
+
.map(|v| v.to_display_string())
|
|
1107
|
+
.unwrap_or_default();
|
|
1108
|
+
if program.is_empty() {
|
|
1109
|
+
return process_capture_obj(0, String::new(), String::new());
|
|
1110
|
+
}
|
|
1111
|
+
let argv: Vec<String> = match args.get(1) {
|
|
1112
|
+
Some(Value::Array(a)) => a.borrow().iter().map(|v| v.to_display_string()).collect(),
|
|
1113
|
+
_ => Vec::new(),
|
|
1114
|
+
};
|
|
1115
|
+
process_capture_result(Command::new(&program).args(&argv).output())
|
|
1116
|
+
}
|
|
1117
|
+
|
|
999
1118
|
#[cfg(all(test, feature = "process", unix))]
|
|
1000
1119
|
mod execfile_tests_384 {
|
|
1001
|
-
use super::process_exec_file;
|
|
1120
|
+
use super::{process_exec_file, process_exec_file_capture};
|
|
1002
1121
|
use tishlang_core::{Value, VmRef};
|
|
1003
1122
|
|
|
1004
1123
|
#[test]
|
|
@@ -1012,6 +1131,47 @@ mod execfile_tests_384 {
|
|
|
1012
1131
|
// empty program is a no-op returning 0, matching `exec`.
|
|
1013
1132
|
assert!(matches!(process_exec_file(&[]), Value::Number(n) if n == 0.0));
|
|
1014
1133
|
}
|
|
1134
|
+
|
|
1135
|
+
// #475: `{ code, stdout, stderr }` capture.
|
|
1136
|
+
fn fields(v: &Value) -> (f64, String, String) {
|
|
1137
|
+
let Value::Object(o) = v else {
|
|
1138
|
+
panic!("execFileCapture must return an object, got {v:?}")
|
|
1139
|
+
};
|
|
1140
|
+
let b = o.borrow();
|
|
1141
|
+
let num = |k: &str| match b.strings.get(k) {
|
|
1142
|
+
Some(Value::Number(n)) => *n,
|
|
1143
|
+
_ => f64::NAN,
|
|
1144
|
+
};
|
|
1145
|
+
let string = |k: &str| match b.strings.get(k) {
|
|
1146
|
+
Some(Value::String(s)) => s.to_string(),
|
|
1147
|
+
_ => String::new(),
|
|
1148
|
+
};
|
|
1149
|
+
(num("code"), string("stdout"), string("stderr"))
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
#[test]
|
|
1153
|
+
fn execfile_capture_returns_code_stdout_stderr() {
|
|
1154
|
+
// Success: stdout captured, code 0, stderr empty.
|
|
1155
|
+
let args = Value::Array(VmRef::new(vec![Value::String("captured".into())]));
|
|
1156
|
+
let (code, out, err) =
|
|
1157
|
+
fields(&process_exec_file_capture(&[Value::String("echo".into()), args]));
|
|
1158
|
+
assert_eq!(code, 0.0);
|
|
1159
|
+
assert_eq!(out.trim(), "captured");
|
|
1160
|
+
assert_eq!(err, "");
|
|
1161
|
+
// Non-zero exit is reported, not an error.
|
|
1162
|
+
let (code, _, _) = fields(&process_exec_file_capture(&[Value::String("false".into())]));
|
|
1163
|
+
assert_eq!(code, 1.0);
|
|
1164
|
+
// Spawn failure → code -1 with the OS error in stderr (never panics).
|
|
1165
|
+
let (code, out, err) =
|
|
1166
|
+
fields(&process_exec_file_capture(&[Value::String("/no/such/prog/xyz".into())]));
|
|
1167
|
+
assert_eq!(code, -1.0);
|
|
1168
|
+
assert!(out.is_empty());
|
|
1169
|
+
assert!(!err.is_empty());
|
|
1170
|
+
// Empty program → 0 / empty (mirrors execFile).
|
|
1171
|
+
let (code, out, err) = fields(&process_exec_file_capture(&[]));
|
|
1172
|
+
assert_eq!(code, 0.0);
|
|
1173
|
+
assert!(out.is_empty() && err.is_empty());
|
|
1174
|
+
}
|
|
1015
1175
|
}
|
|
1016
1176
|
|
|
1017
1177
|
#[cfg(feature = "fs")]
|
|
@@ -1308,6 +1468,11 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
|
|
|
1308
1468
|
Value::Array(arr) => {
|
|
1309
1469
|
let idx = match index {
|
|
1310
1470
|
Value::Number(n) => *n as usize,
|
|
1471
|
+
// Canonical integer string key ("0","1",…) indexes the array (#432); other keys → Null.
|
|
1472
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
1473
|
+
Some(i) => i,
|
|
1474
|
+
None => return Value::Null,
|
|
1475
|
+
},
|
|
1311
1476
|
_ => return Value::Null,
|
|
1312
1477
|
};
|
|
1313
1478
|
arr.borrow().get(idx).cloned().unwrap_or(Value::Null)
|
|
@@ -1316,6 +1481,10 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
|
|
|
1316
1481
|
Value::NumberArray(arr) => {
|
|
1317
1482
|
let idx = match index {
|
|
1318
1483
|
Value::Number(n) => *n as usize,
|
|
1484
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
1485
|
+
Some(i) => i,
|
|
1486
|
+
None => return Value::Null,
|
|
1487
|
+
},
|
|
1319
1488
|
_ => return Value::Null,
|
|
1320
1489
|
};
|
|
1321
1490
|
arr.borrow().get(idx).copied().map(Value::Number).unwrap_or(Value::Null)
|
|
@@ -1416,6 +1585,11 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
|
|
|
1416
1585
|
Value::Array(arr) => {
|
|
1417
1586
|
let index = match idx {
|
|
1418
1587
|
Value::Number(n) => *n as usize,
|
|
1588
|
+
// Canonical integer string key → index (#432); a non-index string key is a no-op write.
|
|
1589
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
1590
|
+
Some(i) => i,
|
|
1591
|
+
None => return val,
|
|
1592
|
+
},
|
|
1419
1593
|
_ => panic!("Array index must be number"),
|
|
1420
1594
|
};
|
|
1421
1595
|
let mut arr_mut = arr.borrow_mut();
|
|
@@ -1432,6 +1606,11 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
|
|
|
1432
1606
|
Value::NumberArray(arr) => {
|
|
1433
1607
|
let index = match idx {
|
|
1434
1608
|
Value::Number(n) => *n as usize,
|
|
1609
|
+
// Canonical integer string key → index (#432); a non-index string key is a no-op write.
|
|
1610
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
1611
|
+
Some(i) => i,
|
|
1612
|
+
None => return val,
|
|
1613
|
+
},
|
|
1435
1614
|
_ => panic!("Array index must be number"),
|
|
1436
1615
|
};
|
|
1437
1616
|
let mut arr_mut = arr.borrow_mut();
|
|
@@ -1530,6 +1709,11 @@ mod timers;
|
|
|
1530
1709
|
#[cfg(any(feature = "http", feature = "promise"))]
|
|
1531
1710
|
mod promise;
|
|
1532
1711
|
|
|
1712
|
+
/// Node-compatible fs surface (sync + `fs/promises`). Referenced by the backends via
|
|
1713
|
+
/// `tishlang_runtime::fs_ext::*`. See issue #122.
|
|
1714
|
+
#[cfg(feature = "fs")]
|
|
1715
|
+
pub mod fs_ext;
|
|
1716
|
+
|
|
1533
1717
|
#[cfg(feature = "http")]
|
|
1534
1718
|
mod native_promise;
|
|
1535
1719
|
|
|
@@ -1830,6 +2014,48 @@ pub fn string_match_regex(s: &Value, regexp: &Value) -> Value {
|
|
|
1830
2014
|
}
|
|
1831
2015
|
}
|
|
1832
2016
|
|
|
2017
|
+
/// `String.prototype.matchAll(regexp)` — an ITERATOR over every match, each an exec-style match object
|
|
2018
|
+
/// (`{ "0", ...groups, index, input, groups }`). Per ECMA the regexp must be global, else a `TypeError`
|
|
2019
|
+
/// is parked; a non-RegExp argument is coerced to a global regex. Reuses `regexp_exec_impl` so the
|
|
2020
|
+
/// match objects are byte-identical to `exec`/`match`, then wraps them in the shared iterator.
|
|
2021
|
+
#[cfg(feature = "regex")]
|
|
2022
|
+
pub fn string_match_all_regex(s: &Value, regexp: &Value) -> Value {
|
|
2023
|
+
let input = match s {
|
|
2024
|
+
Value::String(s) => s.as_ref(),
|
|
2025
|
+
_ => return Value::Null,
|
|
2026
|
+
};
|
|
2027
|
+
let re_cell = match regexp {
|
|
2028
|
+
Value::RegExp(re) => re.clone(),
|
|
2029
|
+
other => {
|
|
2030
|
+
let pattern = other.to_display_string();
|
|
2031
|
+
match tishlang_core::TishRegExp::new(&pattern, "g") {
|
|
2032
|
+
Ok(re) => tishlang_core::VmRef::new(re),
|
|
2033
|
+
Err(_) => return Value::Null,
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
if !re_cell.borrow().flags.global {
|
|
2038
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(
|
|
2039
|
+
"String.prototype.matchAll called with a non-global RegExp argument",
|
|
2040
|
+
));
|
|
2041
|
+
return Value::Null;
|
|
2042
|
+
}
|
|
2043
|
+
let mut results: Vec<Value> = Vec::new();
|
|
2044
|
+
{
|
|
2045
|
+
let mut re = re_cell.borrow_mut();
|
|
2046
|
+
re.last_index = 0;
|
|
2047
|
+
loop {
|
|
2048
|
+
let m = regexp_exec_impl(&mut re, input);
|
|
2049
|
+
if matches!(m, Value::Null) {
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
2052
|
+
results.push(m);
|
|
2053
|
+
}
|
|
2054
|
+
re.last_index = 0;
|
|
2055
|
+
}
|
|
2056
|
+
tishlang_builtins::iterator::array_iterator(results)
|
|
2057
|
+
}
|
|
2058
|
+
|
|
1833
2059
|
#[cfg(feature = "regex")]
|
|
1834
2060
|
fn string_replace_regex_or_callback(s: &Value, search: &Value, replacement: &Value) -> Value {
|
|
1835
2061
|
let input = match s {
|
|
@@ -188,7 +188,12 @@ fn collect_fun_decl_names_stmt(stmt: &Statement, names: &mut HashSet<String>) {
|
|
|
188
188
|
}
|
|
189
189
|
collect_fun_decl_names_stmt(body, names);
|
|
190
190
|
}
|
|
191
|
-
Statement::ForOf { iterable, body, .. }
|
|
191
|
+
Statement::ForOf { iterable, body, .. }
|
|
192
|
+
| Statement::ForIn {
|
|
193
|
+
object: iterable,
|
|
194
|
+
body,
|
|
195
|
+
..
|
|
196
|
+
} => {
|
|
192
197
|
collect_fun_decl_names_expr(iterable, names);
|
|
193
198
|
collect_fun_decl_names_stmt(body, names);
|
|
194
199
|
}
|
|
@@ -646,7 +651,12 @@ fn stmt_contains_jsx_fragment(stmt: &tishlang_ast::Statement) -> bool {
|
|
|
646
651
|
|| update.as_ref().is_some_and(expr_contains_jsx_fragment)
|
|
647
652
|
|| stmt_contains_jsx_fragment(body)
|
|
648
653
|
}
|
|
649
|
-
Statement::ForOf { iterable, body, .. }
|
|
654
|
+
Statement::ForOf { iterable, body, .. }
|
|
655
|
+
| Statement::ForIn {
|
|
656
|
+
object: iterable,
|
|
657
|
+
body,
|
|
658
|
+
..
|
|
659
|
+
} => {
|
|
650
660
|
expr_contains_jsx_fragment(iterable) || stmt_contains_jsx_fragment(body)
|
|
651
661
|
}
|
|
652
662
|
Statement::Switch {
|
|
@@ -827,7 +837,12 @@ fn stmt_contains_jsx(stmt: &tishlang_ast::Statement) -> bool {
|
|
|
827
837
|
|| update.as_ref().is_some_and(expr_contains_jsx)
|
|
828
838
|
|| stmt_contains_jsx(body)
|
|
829
839
|
}
|
|
830
|
-
Statement::ForOf { iterable, body, .. }
|
|
840
|
+
Statement::ForOf { iterable, body, .. }
|
|
841
|
+
| Statement::ForIn {
|
|
842
|
+
object: iterable,
|
|
843
|
+
body,
|
|
844
|
+
..
|
|
845
|
+
} => {
|
|
831
846
|
expr_contains_jsx(iterable) || stmt_contains_jsx(body)
|
|
832
847
|
}
|
|
833
848
|
Statement::Switch {
|
|
@@ -3634,7 +3634,7 @@ mod tests {
|
|
|
3634
3634
|
#[test]
|
|
3635
3635
|
fn osr_region_handles_branches() {
|
|
3636
3636
|
let chunk = top_chunk(
|
|
3637
|
-
"let s = 0.0\nlet i = 0.0\nwhile (i < 20.0) { if (i % 2.0
|
|
3637
|
+
"let s = 0.0\nlet i = 0.0\nwhile (i < 20.0) { if (i % 2.0 === 0.0) { s = s + i }; i = i + 1.0 }\n",
|
|
3638
3638
|
);
|
|
3639
3639
|
let (header, end) = first_region(&chunk);
|
|
3640
3640
|
let lf =
|