@tishlang/tish 1.13.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +1 -0
- package/crates/tish/Cargo.toml +11 -3
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cli_help.rs +15 -4
- package/crates/tish/src/main.rs +93 -21
- package/crates/tish/src/repl_completion.rs +0 -1
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +402 -91
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/src/ast.rs +37 -8
- package/crates/tish_builtins/Cargo.toml +2 -0
- package/crates/tish_builtins/src/array.rs +375 -13
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +59 -19
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +86 -6
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +5 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +2 -2
- package/crates/tish_builtins/src/string.rs +19 -20
- package/crates/tish_builtins/src/symbol.rs +1 -1
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/src/chunk.rs +69 -1
- package/crates/tish_bytecode/src/compiler.rs +933 -89
- package/crates/tish_bytecode/src/encoding.rs +2 -0
- package/crates/tish_bytecode/src/lib.rs +2 -1
- package/crates/tish_bytecode/src/opcode.rs +47 -4
- package/crates/tish_bytecode/src/serialize.rs +31 -1
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +2334 -349
- package/crates/tish_compile/src/infer.rs +1395 -6
- package/crates/tish_compile/src/lib.rs +50 -8
- package/crates/tish_compile/src/resolve.rs +584 -21
- package/crates/tish_compile/src/types.rs +106 -2
- package/crates/tish_compile_js/src/codegen.rs +67 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +64 -0
- package/crates/tish_core/Cargo.toml +7 -1
- package/crates/tish_core/src/console_style.rs +11 -1
- package/crates/tish_core/src/json.rs +81 -38
- package/crates/tish_core/src/lib.rs +3 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/value.rs +679 -25
- package/crates/tish_core/src/vmref.rs +13 -8
- package/crates/tish_cranelift/src/link.rs +17 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +1 -0
- package/crates/tish_eval/Cargo.toml +6 -0
- package/crates/tish_eval/src/eval.rs +665 -117
- package/crates/tish_eval/src/http.rs +4 -1
- package/crates/tish_eval/src/natives.rs +165 -13
- package/crates/tish_eval/src/value.rs +31 -13
- package/crates/tish_eval/src/value_convert.rs +10 -4
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/src/lib.rs +61 -5
- package/crates/tish_lexer/src/lib.rs +397 -9
- package/crates/tish_lexer/src/token.rs +7 -0
- package/crates/tish_lint/src/lib.rs +2 -10
- package/crates/tish_lsp/src/import_goto.rs +2 -0
- package/crates/tish_lsp/src/main.rs +439 -26
- package/crates/tish_native/src/build.rs +55 -1
- package/crates/tish_opt/src/lib.rs +126 -23
- package/crates/tish_parser/src/lib.rs +55 -1
- package/crates/tish_parser/src/parser.rs +456 -34
- package/crates/tish_pg/src/lib.rs +3 -3
- package/crates/tish_resolve/src/lib.rs +99 -59
- package/crates/tish_runtime/Cargo.toml +4 -0
- package/crates/tish_runtime/src/http.rs +66 -17
- package/crates/tish_runtime/src/http_fetch.rs +29 -8
- package/crates/tish_runtime/src/http_hyper.rs +25 -2
- package/crates/tish_runtime/src/lib.rs +299 -44
- package/crates/tish_runtime/src/promise.rs +328 -18
- package/crates/tish_runtime/src/timers.rs +13 -7
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +35 -18
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +2 -2
- package/crates/tish_ui/src/jsx.rs +10 -0
- package/crates/tish_ui/src/runtime/hooks.rs +19 -15
- package/crates/tish_ui/src/runtime/mod.rs +15 -12
- package/crates/tish_vm/Cargo.toml +14 -1
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +2 -0
- package/crates/tish_vm/src/vm.rs +1546 -202
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_wasm/src/lib.rs +6 -2
- package/crates/tish_wasm_runtime/src/gpu.rs +17 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/lib.rs +2 -2
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +1 -1
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -12,16 +12,47 @@ use tishlang_builtins::helpers::make_error_value;
|
|
|
12
12
|
pub use tishlang_builtins::symbol::symbol_object;
|
|
13
13
|
pub use tishlang_core::ObjectMap;
|
|
14
14
|
pub use tishlang_core::Value;
|
|
15
|
+
pub use tishlang_core::ArcStr;
|
|
15
16
|
/// Used by native codegen for `f()` / `obj()` dispatch (`Value::Function` or `__call` on objects).
|
|
16
17
|
pub use tishlang_core::value_call;
|
|
18
|
+
/// JS ToInt32/ToUint32 for the emitted bitwise/shift code (modulo 2³², NaN/±Infinity → 0).
|
|
19
|
+
pub use tishlang_core::{to_int32, to_uint32};
|
|
17
20
|
// Re-export the shared-mutable wrapper so the Rust code emitted by
|
|
18
21
|
// `tishlang_compile::codegen` can write `VmRef::new(...)` without needing
|
|
19
22
|
// a direct dependency on `tishlang_core` from the generated crate.
|
|
20
23
|
pub use tishlang_core::{VmReadGuard, VmRef, VmWriteGuard};
|
|
21
24
|
|
|
25
|
+
/// `for…of` iterable normalization for the native backend: a JS iterator object (one with a
|
|
26
|
+
/// callable `next()` returning `{ value, done }`, e.g. a `Map`/`Set` `.values()` result) is
|
|
27
|
+
/// drained into a `Value::Array`; arrays, strings, and everything else pass through unchanged.
|
|
28
|
+
pub fn normalize_for_of(v: Value) -> Value {
|
|
29
|
+
match tishlang_core::drain_iterator(&v) {
|
|
30
|
+
Some(items) => Value::Array(VmRef::new(items)),
|
|
31
|
+
None => v,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub use tishlang_builtins::construct::array_construct;
|
|
36
|
+
pub use tishlang_builtins::construct::error_constructor_value as tish_error_constructor;
|
|
22
37
|
pub use tishlang_builtins::construct::{
|
|
23
38
|
audio_context_constructor_value as tish_audio_context_constructor, construct as tish_construct,
|
|
39
|
+
};
|
|
40
|
+
pub use tishlang_builtins::typedarrays::{
|
|
41
|
+
float32_array_constructor_value as tish_float32_array_constructor,
|
|
42
|
+
float64_array_constructor_value as tish_float64_array_constructor,
|
|
43
|
+
float64_array_packed,
|
|
44
|
+
int16_array_constructor_value as tish_int16_array_constructor,
|
|
45
|
+
int32_array_constructor_value as tish_int32_array_constructor,
|
|
46
|
+
int8_array_constructor_value as tish_int8_array_constructor,
|
|
47
|
+
uint16_array_constructor_value as tish_uint16_array_constructor,
|
|
48
|
+
uint32_array_constructor_value as tish_uint32_array_constructor,
|
|
24
49
|
uint8_array_constructor_value as tish_uint8_array_constructor,
|
|
50
|
+
uint8_clamped_array_constructor_value as tish_uint8_clamped_array_constructor,
|
|
51
|
+
};
|
|
52
|
+
pub use tishlang_builtins::date::date_constructor_value as tish_date_constructor;
|
|
53
|
+
pub use tishlang_builtins::collections::{
|
|
54
|
+
collection_size, map_constructor_value as tish_map_constructor,
|
|
55
|
+
set_constructor_value as tish_set_constructor,
|
|
25
56
|
};
|
|
26
57
|
|
|
27
58
|
// Re-export array methods from tishlang_builtins
|
|
@@ -30,7 +61,7 @@ pub use tishlang_builtins::array::{
|
|
|
30
61
|
find_index as array_find_index, flat as array_flat_impl, flat_map as array_flat_map,
|
|
31
62
|
for_each as array_for_each, includes as array_includes_impl, index_of as array_index_of_impl,
|
|
32
63
|
join as array_join_impl, map as array_map, pop as array_pop, push as array_push_impl,
|
|
33
|
-
reduce as array_reduce, reverse as array_reverse, shift as array_shift,
|
|
64
|
+
fill as array_fill, reduce as array_reduce, reverse as array_reverse, shift as array_shift,
|
|
34
65
|
shuffle as array_shuffle, slice as array_slice_impl, some as array_some,
|
|
35
66
|
sort_default as array_sort_default, sort_numeric_asc as array_sort_numeric_asc,
|
|
36
67
|
sort_numeric_desc as array_sort_numeric_desc,
|
|
@@ -110,6 +141,12 @@ pub fn string_substr(s: &Value, start: &Value, length: &Value) -> Value {
|
|
|
110
141
|
string_substr_impl(s, start, length)
|
|
111
142
|
}
|
|
112
143
|
pub fn string_split(s: &Value, sep: &Value) -> Value {
|
|
144
|
+
// A RegExp separator routes to the regex splitter (matches string_replace's regex handling
|
|
145
|
+
// and the interpreter/VM), so `"a1b2c".split(RegExp("\\d",""))` works on the rust backend.
|
|
146
|
+
#[cfg(feature = "regex")]
|
|
147
|
+
if matches!(sep, Value::RegExp(_)) {
|
|
148
|
+
return string_split_regex(s, sep, None);
|
|
149
|
+
}
|
|
113
150
|
string_split_impl(s, sep)
|
|
114
151
|
}
|
|
115
152
|
pub fn string_starts_with(s: &Value, search: &Value) -> Value {
|
|
@@ -148,16 +185,23 @@ pub fn string_last_index_of(s: &Value, search: &Value, position: &Value) -> Valu
|
|
|
148
185
|
}
|
|
149
186
|
|
|
150
187
|
/// Number.prototype.toFixed(digits) - format number with fixed decimal places (0-20)
|
|
188
|
+
///
|
|
189
|
+
/// Delegates to the single source of truth in `tishlang_builtins::number` so the rust
|
|
190
|
+
/// backend, the bytecode VM, and the interpreter stay byte-identical. See
|
|
191
|
+
/// `tish/docs/full-backend-parity-plan.md` (Workstream A).
|
|
151
192
|
pub fn number_to_fixed(n: &Value, digits: &Value) -> Value {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
193
|
+
tishlang_builtins::number::to_fixed(n, digits)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// `.toString([radix])` for the compiled backend (issue #59). A number receiver uses the
|
|
197
|
+
/// shared radix formatter so it stays byte-identical with the VM / interpreter; any other
|
|
198
|
+
/// receiver falls back to its normal JS string, so `[1,2].toString()` / `obj.toString()`
|
|
199
|
+
/// keep working.
|
|
200
|
+
pub fn number_to_string(n: &Value, radix: &Value) -> Value {
|
|
201
|
+
match n {
|
|
202
|
+
Value::Number(_) => tishlang_builtins::number::to_string(n, radix),
|
|
203
|
+
other => Value::String(other.to_js_string().into()),
|
|
204
|
+
}
|
|
161
205
|
}
|
|
162
206
|
|
|
163
207
|
/// Operators module for compound assignment operations
|
|
@@ -175,20 +219,26 @@ pub mod ops {
|
|
|
175
219
|
Ok(Value::String(s.into()))
|
|
176
220
|
}
|
|
177
221
|
(Value::String(a), b) => {
|
|
178
|
-
let b_str = b.
|
|
222
|
+
let b_str = b.to_js_string();
|
|
179
223
|
let mut s = String::with_capacity(a.len() + b_str.len());
|
|
180
224
|
s.push_str(a);
|
|
181
225
|
s.push_str(&b_str);
|
|
182
226
|
Ok(Value::String(s.into()))
|
|
183
227
|
}
|
|
184
228
|
(a, Value::String(b)) => {
|
|
185
|
-
let a_str = a.
|
|
229
|
+
let a_str = a.to_js_string();
|
|
186
230
|
let mut s = String::with_capacity(a_str.len() + b.len());
|
|
187
231
|
s.push_str(&a_str);
|
|
188
232
|
s.push_str(b);
|
|
189
233
|
Ok(Value::String(s.into()))
|
|
190
234
|
}
|
|
191
|
-
|
|
235
|
+
// Neither operand is a string here ⇒ numeric coercion, matching the VM's `eval_binop`
|
|
236
|
+
// (`as_number().unwrap_or(NaN)`): a null/bool/object operand (e.g. an out-of-bounds array
|
|
237
|
+
// read) coerces to NaN, so `number + null` is NaN — NOT an error that the codegen's
|
|
238
|
+
// `.unwrap_or(Value::Null)` would silently turn into `null` (the old rust-AOT divergence).
|
|
239
|
+
(a, b) => Ok(Value::Number(
|
|
240
|
+
a.as_number().unwrap_or(f64::NAN) + b.as_number().unwrap_or(f64::NAN),
|
|
241
|
+
)),
|
|
192
242
|
}
|
|
193
243
|
}
|
|
194
244
|
|
|
@@ -196,7 +246,10 @@ pub mod ops {
|
|
|
196
246
|
pub fn sub(left: &Value, right: &Value) -> Result<Value, Box<dyn std::error::Error>> {
|
|
197
247
|
match (left, right) {
|
|
198
248
|
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b)),
|
|
199
|
-
|
|
249
|
+
// VM-parity numeric coercion (null/non-number → NaN), see `add`.
|
|
250
|
+
(a, b) => Ok(Value::Number(
|
|
251
|
+
a.as_number().unwrap_or(f64::NAN) - b.as_number().unwrap_or(f64::NAN),
|
|
252
|
+
)),
|
|
200
253
|
}
|
|
201
254
|
}
|
|
202
255
|
|
|
@@ -204,7 +257,10 @@ pub mod ops {
|
|
|
204
257
|
pub fn mul(left: &Value, right: &Value) -> Result<Value, Box<dyn std::error::Error>> {
|
|
205
258
|
match (left, right) {
|
|
206
259
|
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a * b)),
|
|
207
|
-
|
|
260
|
+
// VM-parity numeric coercion (null/non-number → NaN), see `add`.
|
|
261
|
+
(a, b) => Ok(Value::Number(
|
|
262
|
+
a.as_number().unwrap_or(f64::NAN) * b.as_number().unwrap_or(f64::NAN),
|
|
263
|
+
)),
|
|
208
264
|
}
|
|
209
265
|
}
|
|
210
266
|
|
|
@@ -212,7 +268,10 @@ pub mod ops {
|
|
|
212
268
|
pub fn div(left: &Value, right: &Value) -> Result<Value, Box<dyn std::error::Error>> {
|
|
213
269
|
match (left, right) {
|
|
214
270
|
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a / b)),
|
|
215
|
-
|
|
271
|
+
// VM-parity numeric coercion (null/non-number → NaN), see `add`.
|
|
272
|
+
(a, b) => Ok(Value::Number(
|
|
273
|
+
a.as_number().unwrap_or(f64::NAN) / b.as_number().unwrap_or(f64::NAN),
|
|
274
|
+
)),
|
|
216
275
|
}
|
|
217
276
|
}
|
|
218
277
|
|
|
@@ -221,7 +280,7 @@ pub mod ops {
|
|
|
221
280
|
pub fn lt(left: &Value, right: &Value) -> Value {
|
|
222
281
|
let b = match (left, right) {
|
|
223
282
|
(Value::Number(a), Value::Number(b)) => a < b,
|
|
224
|
-
(Value::String(a), Value::String(b)) => a.
|
|
283
|
+
(Value::String(a), Value::String(b)) => a.as_str() < b.as_str(),
|
|
225
284
|
_ => false,
|
|
226
285
|
};
|
|
227
286
|
Value::Bool(b)
|
|
@@ -231,7 +290,7 @@ pub mod ops {
|
|
|
231
290
|
pub fn le(left: &Value, right: &Value) -> Value {
|
|
232
291
|
let b = match (left, right) {
|
|
233
292
|
(Value::Number(a), Value::Number(b)) => a <= b,
|
|
234
|
-
(Value::String(a), Value::String(b)) => a.
|
|
293
|
+
(Value::String(a), Value::String(b)) => a.as_str() <= b.as_str(),
|
|
235
294
|
_ => false,
|
|
236
295
|
};
|
|
237
296
|
Value::Bool(b)
|
|
@@ -241,7 +300,7 @@ pub mod ops {
|
|
|
241
300
|
pub fn gt(left: &Value, right: &Value) -> Value {
|
|
242
301
|
let b = match (left, right) {
|
|
243
302
|
(Value::Number(a), Value::Number(b)) => a > b,
|
|
244
|
-
(Value::String(a), Value::String(b)) => a.
|
|
303
|
+
(Value::String(a), Value::String(b)) => a.as_str() > b.as_str(),
|
|
245
304
|
_ => false,
|
|
246
305
|
};
|
|
247
306
|
Value::Bool(b)
|
|
@@ -251,7 +310,7 @@ pub mod ops {
|
|
|
251
310
|
pub fn ge(left: &Value, right: &Value) -> Value {
|
|
252
311
|
let b = match (left, right) {
|
|
253
312
|
(Value::Number(a), Value::Number(b)) => a >= b,
|
|
254
|
-
(Value::String(a), Value::String(b)) => a.
|
|
313
|
+
(Value::String(a), Value::String(b)) => a.as_str() >= b.as_str(),
|
|
255
314
|
_ => false,
|
|
256
315
|
};
|
|
257
316
|
Value::Bool(b)
|
|
@@ -261,7 +320,10 @@ pub mod ops {
|
|
|
261
320
|
pub fn modulo(left: &Value, right: &Value) -> Result<Value, Box<dyn std::error::Error>> {
|
|
262
321
|
match (left, right) {
|
|
263
322
|
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a % b)),
|
|
264
|
-
|
|
323
|
+
// VM-parity numeric coercion (null/non-number → NaN), see `add`.
|
|
324
|
+
(a, b) => Ok(Value::Number(
|
|
325
|
+
a.as_number().unwrap_or(f64::NAN) % b.as_number().unwrap_or(f64::NAN),
|
|
326
|
+
)),
|
|
265
327
|
}
|
|
266
328
|
}
|
|
267
329
|
}
|
|
@@ -273,6 +335,8 @@ use tishlang_builtins::globals::{
|
|
|
273
335
|
object_assign as builtins_object_assign, object_entries as builtins_object_entries,
|
|
274
336
|
object_from_entries as builtins_object_from_entries, object_keys as builtins_object_keys,
|
|
275
337
|
object_values as builtins_object_values,
|
|
338
|
+
number_convert as builtins_number_convert,
|
|
339
|
+
string_convert as builtins_string_convert,
|
|
276
340
|
string_from_char_code as builtins_string_from_char_code,
|
|
277
341
|
};
|
|
278
342
|
use tishlang_core::{json_parse as core_json_parse, json_stringify as core_json_stringify};
|
|
@@ -321,22 +385,41 @@ pub mod json {
|
|
|
321
385
|
}
|
|
322
386
|
}
|
|
323
387
|
|
|
324
|
-
/// Error type for Tish throw/catch
|
|
388
|
+
/// Error type for Tish throw/catch + non-local control flow (used to model `return`/`throw`
|
|
389
|
+
/// escaping `try`/`finally` in the Rust backend, which has no native exceptions).
|
|
325
390
|
#[derive(Debug, Clone)]
|
|
326
391
|
pub enum TishError {
|
|
392
|
+
/// A JS `throw` — catchable by `catch`.
|
|
327
393
|
Throw(Value),
|
|
394
|
+
/// A JS `return value` that must escape an enclosing `try`/`finally` and unwind to the
|
|
395
|
+
/// function boundary (running each `finally` on the way out). Never caught by `catch`.
|
|
396
|
+
Return(Value),
|
|
328
397
|
}
|
|
329
398
|
|
|
330
399
|
impl fmt::Display for TishError {
|
|
331
400
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
332
401
|
match self {
|
|
333
402
|
TishError::Throw(v) => write!(f, "{}", v.to_display_string()),
|
|
403
|
+
TishError::Return(v) => write!(f, "return {}", v.to_display_string()),
|
|
334
404
|
}
|
|
335
405
|
}
|
|
336
406
|
}
|
|
337
407
|
|
|
338
408
|
impl std::error::Error for TishError {}
|
|
339
409
|
|
|
410
|
+
/// Function-boundary unwind: convert a completion that escaped a function body's `Result`-closure
|
|
411
|
+
/// back into the function's `Value`. A `return v` yields `v`; an uncaught `throw` panics (matching
|
|
412
|
+
/// the behavior of a throw with no enclosing `try`); any other error panics.
|
|
413
|
+
pub fn fn_unwind(e: Box<dyn std::error::Error>) -> Value {
|
|
414
|
+
match e.downcast::<TishError>() {
|
|
415
|
+
Ok(te) => match *te {
|
|
416
|
+
TishError::Return(v) => v,
|
|
417
|
+
TishError::Throw(v) => panic!("uncaught throw: {}", v.to_display_string()),
|
|
418
|
+
},
|
|
419
|
+
Err(orig) => panic!("error in native Tish: {:?}", orig),
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
340
423
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
341
424
|
enum LogLevel {
|
|
342
425
|
Debug = 0,
|
|
@@ -483,6 +566,26 @@ pub fn math_log(args: &[Value]) -> Value {
|
|
|
483
566
|
Value::Number(n.ln())
|
|
484
567
|
}
|
|
485
568
|
|
|
569
|
+
// Hyperbolic / inverse-hyperbolic / cbrt / base-2/10 logs (issue #61). Compiled backends
|
|
570
|
+
// (native/cranelift/wasi) share this runtime, so wiring them here resolves all of them.
|
|
571
|
+
macro_rules! runtime_math_unary {
|
|
572
|
+
($name:ident, $method:ident) => {
|
|
573
|
+
pub fn $name(args: &[Value]) -> Value {
|
|
574
|
+
let n = extract_num(args.first()).unwrap_or(f64::NAN);
|
|
575
|
+
Value::Number(n.$method())
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
runtime_math_unary!(math_sinh, sinh);
|
|
580
|
+
runtime_math_unary!(math_cosh, cosh);
|
|
581
|
+
runtime_math_unary!(math_tanh, tanh);
|
|
582
|
+
runtime_math_unary!(math_asinh, asinh);
|
|
583
|
+
runtime_math_unary!(math_acosh, acosh);
|
|
584
|
+
runtime_math_unary!(math_atanh, atanh);
|
|
585
|
+
runtime_math_unary!(math_cbrt, cbrt);
|
|
586
|
+
runtime_math_unary!(math_log2, log2);
|
|
587
|
+
runtime_math_unary!(math_log10, log10);
|
|
588
|
+
|
|
486
589
|
pub fn json_stringify(args: &[Value]) -> Value {
|
|
487
590
|
let v = args.first().cloned().unwrap_or(Value::Null);
|
|
488
591
|
Value::String(core_json_stringify(&v).into())
|
|
@@ -496,14 +599,6 @@ pub fn json_parse(args: &[Value]) -> Value {
|
|
|
496
599
|
core_json_parse(&s).unwrap_or(Value::Null)
|
|
497
600
|
}
|
|
498
601
|
|
|
499
|
-
pub fn date_now(_args: &[Value]) -> Value {
|
|
500
|
-
use std::time::{SystemTime, UNIX_EPOCH};
|
|
501
|
-
let now = SystemTime::now()
|
|
502
|
-
.duration_since(UNIX_EPOCH)
|
|
503
|
-
.map(|d| d.as_millis() as f64)
|
|
504
|
-
.unwrap_or(0.0);
|
|
505
|
-
Value::Number(now)
|
|
506
|
-
}
|
|
507
602
|
|
|
508
603
|
pub fn array_is_array(args: &[Value]) -> Value {
|
|
509
604
|
builtins_array_is_array(args)
|
|
@@ -513,6 +608,16 @@ pub fn string_from_char_code(args: &[Value]) -> Value {
|
|
|
513
608
|
builtins_string_from_char_code(args)
|
|
514
609
|
}
|
|
515
610
|
|
|
611
|
+
/// `String(value)` as a function (JS `ToString`). Wired into the codegen `String`
|
|
612
|
+
/// global as `__call` so compiled `String(x)` matches the VM/interp.
|
|
613
|
+
pub fn string_convert(args: &[Value]) -> Value {
|
|
614
|
+
builtins_string_convert(args)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
pub fn number_convert(args: &[Value]) -> Value {
|
|
618
|
+
builtins_number_convert(args)
|
|
619
|
+
}
|
|
620
|
+
|
|
516
621
|
#[cfg(feature = "process")]
|
|
517
622
|
pub fn process_exit(args: &[Value]) -> Value {
|
|
518
623
|
let code = args
|
|
@@ -632,6 +737,13 @@ pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
|
|
|
632
737
|
let key = key.as_ref();
|
|
633
738
|
match obj {
|
|
634
739
|
Value::Object(map) => {
|
|
740
|
+
// `Set`/`Map` instances expose a computed `.size` (the backing store has no real
|
|
741
|
+
// `size` key); `collection_size` returns `None` for any other object.
|
|
742
|
+
if key == "size" {
|
|
743
|
+
if let Some(n) = collection_size(obj) {
|
|
744
|
+
return Value::Number(n);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
635
747
|
// The map's key type is `Arc<str>`, which implements
|
|
636
748
|
// `Borrow<str>` — so we can look up with a borrowed `&str`
|
|
637
749
|
// directly. Previously we allocated a fresh `Arc<str>` on
|
|
@@ -648,6 +760,17 @@ pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
|
|
|
648
760
|
Value::Null
|
|
649
761
|
}
|
|
650
762
|
}
|
|
763
|
+
// Packed `Float64Array` (`TISH_PACKED_ARRAYS`): `.length` and numeric-key reads, mirroring
|
|
764
|
+
// the boxed `Array` arm. Methods (`reduce`/`map`/…) materialise via `as_boxed_array`.
|
|
765
|
+
Value::NumberArray(arr) => {
|
|
766
|
+
if key == "length" {
|
|
767
|
+
Value::Number(arr.borrow().len() as f64)
|
|
768
|
+
} else if let Ok(idx) = key.parse::<usize>() {
|
|
769
|
+
arr.borrow().get(idx).copied().map(Value::Number).unwrap_or(Value::Null)
|
|
770
|
+
} else {
|
|
771
|
+
Value::Null
|
|
772
|
+
}
|
|
773
|
+
}
|
|
651
774
|
Value::String(s) => {
|
|
652
775
|
if key == "length" {
|
|
653
776
|
Value::Number(s.chars().count() as f64)
|
|
@@ -656,26 +779,53 @@ pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
|
|
|
656
779
|
}
|
|
657
780
|
}
|
|
658
781
|
#[cfg(feature = "regex")]
|
|
659
|
-
Value::RegExp(re) => {
|
|
660
|
-
|
|
661
|
-
|
|
782
|
+
Value::RegExp(re) => match key {
|
|
783
|
+
"exec" => {
|
|
784
|
+
let rc = re.clone();
|
|
662
785
|
Value::native(move |args: &[Value]| {
|
|
663
786
|
let input = args.first().unwrap_or(&Value::Null);
|
|
664
|
-
regexp_exec(&Value::RegExp(
|
|
787
|
+
regexp_exec(&Value::RegExp(rc.clone()), input)
|
|
665
788
|
})
|
|
666
|
-
}
|
|
789
|
+
}
|
|
790
|
+
"test" => {
|
|
791
|
+
let rc = re.clone();
|
|
667
792
|
Value::native(move |args: &[Value]| {
|
|
668
793
|
let input = args.first().unwrap_or(&Value::Null);
|
|
669
|
-
regexp_test(&Value::RegExp(
|
|
794
|
+
regexp_test(&Value::RegExp(rc.clone()), input)
|
|
670
795
|
})
|
|
671
|
-
} else {
|
|
672
|
-
Value::Null
|
|
673
796
|
}
|
|
674
|
-
|
|
797
|
+
// Properties — mirror the interpreter + bytecode VM so all backends agree.
|
|
798
|
+
"source" => Value::String(re.borrow().source.clone().into()),
|
|
799
|
+
"flags" => Value::String(re.borrow().flags_string().into()),
|
|
800
|
+
"lastIndex" => Value::Number(re.borrow().last_index as f64),
|
|
801
|
+
"global" => Value::Bool(re.borrow().flags.global),
|
|
802
|
+
"ignoreCase" => Value::Bool(re.borrow().flags.ignore_case),
|
|
803
|
+
"multiline" => Value::Bool(re.borrow().flags.multiline),
|
|
804
|
+
"dotAll" => Value::Bool(re.borrow().flags.dot_all),
|
|
805
|
+
"unicode" => Value::Bool(re.borrow().flags.unicode),
|
|
806
|
+
"sticky" => Value::Bool(re.borrow().flags.sticky),
|
|
807
|
+
_ => Value::Null,
|
|
808
|
+
},
|
|
675
809
|
Value::Opaque(o) => o
|
|
676
810
|
.get_method(key)
|
|
677
811
|
.map(Value::Function)
|
|
678
812
|
.unwrap_or(Value::Null),
|
|
813
|
+
// Promise instance methods (`.then`/`.catch`), bound to this promise. Returning a
|
|
814
|
+
// callable here makes the rust backend match the VM family (interp/vm/cranelift/wasi),
|
|
815
|
+
// which expose these via `GetMember`. Both `p.then(cb)` (member) and `p["catch"](cb)`
|
|
816
|
+
// (index, used because `catch` is reserved) route through here / `get_index`.
|
|
817
|
+
#[cfg(any(feature = "http", feature = "promise"))]
|
|
818
|
+
Value::Promise(p) => match key {
|
|
819
|
+
"then" => {
|
|
820
|
+
let pc = p.clone();
|
|
821
|
+
Value::native(move |args: &[Value]| promise_instance_then(&pc, args))
|
|
822
|
+
}
|
|
823
|
+
"catch" => {
|
|
824
|
+
let pc = p.clone();
|
|
825
|
+
Value::native(move |args: &[Value]| promise_instance_catch(&pc, args))
|
|
826
|
+
}
|
|
827
|
+
_ => Value::Null,
|
|
828
|
+
},
|
|
679
829
|
_ => Value::Null,
|
|
680
830
|
}
|
|
681
831
|
}
|
|
@@ -690,11 +840,65 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
|
|
|
690
840
|
};
|
|
691
841
|
arr.borrow().get(idx).cloned().unwrap_or(Value::Null)
|
|
692
842
|
}
|
|
843
|
+
// Packed `Float64Array` indexing (`TISH_PACKED_ARRAYS`); mirrors the boxed `Array` arm.
|
|
844
|
+
Value::NumberArray(arr) => {
|
|
845
|
+
let idx = match index {
|
|
846
|
+
Value::Number(n) => *n as usize,
|
|
847
|
+
_ => return Value::Null,
|
|
848
|
+
};
|
|
849
|
+
arr.borrow().get(idx).copied().map(Value::Number).unwrap_or(Value::Null)
|
|
850
|
+
}
|
|
851
|
+
// `str[i]` returns the character at index `i` (issue #17) — matches the VM /
|
|
852
|
+
// interpreter; out-of-bounds / negative / non-integer indices yield null.
|
|
853
|
+
Value::String(s) => match index {
|
|
854
|
+
Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => s
|
|
855
|
+
.chars()
|
|
856
|
+
.nth(*n as usize)
|
|
857
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
858
|
+
.unwrap_or(Value::Null),
|
|
859
|
+
_ => Value::Null,
|
|
860
|
+
},
|
|
693
861
|
Value::Object(_) => tishlang_core::object_get(obj, index).unwrap_or(Value::Null),
|
|
862
|
+
// `promise["then"|"catch"]` — string-keyed access mirrors `get_prop` (bracket form
|
|
863
|
+
// is required for `catch`, a reserved word). Keeps the rust backend on par with the VM.
|
|
864
|
+
#[cfg(any(feature = "http", feature = "promise"))]
|
|
865
|
+
Value::Promise(_) => match index {
|
|
866
|
+
Value::String(k) => get_prop(obj, k.as_str()),
|
|
867
|
+
_ => Value::Null,
|
|
868
|
+
},
|
|
694
869
|
_ => Value::Null,
|
|
695
870
|
}
|
|
696
871
|
}
|
|
697
872
|
|
|
873
|
+
/// `delete obj[key]` / `delete obj.prop` (issue #40). Objects drop the string key; arrays
|
|
874
|
+
/// clear a numeric index to a `null` hole (length preserved). Always evaluates to `true`.
|
|
875
|
+
#[inline]
|
|
876
|
+
pub fn delete_property(obj: &Value, key: &Value) -> Value {
|
|
877
|
+
match obj {
|
|
878
|
+
Value::Object(m) => {
|
|
879
|
+
let key_s = match key {
|
|
880
|
+
Value::String(s) => s.to_string(),
|
|
881
|
+
other => other.to_js_string(),
|
|
882
|
+
};
|
|
883
|
+
m.borrow_mut().strings.remove(key_s.as_str());
|
|
884
|
+
}
|
|
885
|
+
Value::Array(arr) => {
|
|
886
|
+
if let Value::Number(n) = key {
|
|
887
|
+
let n = *n;
|
|
888
|
+
if n >= 0.0 && n.fract() == 0.0 {
|
|
889
|
+
let i = n as usize;
|
|
890
|
+
let mut a = arr.borrow_mut();
|
|
891
|
+
if i < a.len() {
|
|
892
|
+
a[i] = Value::Null;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
_ => {}
|
|
898
|
+
}
|
|
899
|
+
Value::Bool(true)
|
|
900
|
+
}
|
|
901
|
+
|
|
698
902
|
#[inline]
|
|
699
903
|
pub fn set_prop(obj: &Value, key: &str, val: Value) -> Value {
|
|
700
904
|
match obj {
|
|
@@ -710,6 +914,16 @@ pub fn set_prop(obj: &Value, key: &str, val: Value) -> Value {
|
|
|
710
914
|
}
|
|
711
915
|
val
|
|
712
916
|
}
|
|
917
|
+
// `arr.length = k` truncates / grows the array (holes read back as Null), matching
|
|
918
|
+
// JS and the VM/interpreter (issue #62).
|
|
919
|
+
Value::Array(arr) if key == "length" => {
|
|
920
|
+
let n = extract_num(Some(&val)).unwrap_or(f64::NAN);
|
|
921
|
+
if n.is_nan() || n < 0.0 || n.fract() != 0.0 || n > 4_294_967_295.0 {
|
|
922
|
+
panic!("Invalid array length");
|
|
923
|
+
}
|
|
924
|
+
arr.borrow_mut().resize(n as usize, Value::Null);
|
|
925
|
+
val
|
|
926
|
+
}
|
|
713
927
|
_ => panic!("Cannot assign property on non-object"),
|
|
714
928
|
}
|
|
715
929
|
}
|
|
@@ -729,6 +943,22 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
|
|
|
729
943
|
arr_mut[index] = val.clone();
|
|
730
944
|
val
|
|
731
945
|
}
|
|
946
|
+
// Packed `Float64Array` write (`TISH_PACKED_ARRAYS`). On the native path a `NumberArray` is
|
|
947
|
+
// always a `Float64Array`, so storing the f64 (non-numeric → `NaN`) is the correct view
|
|
948
|
+
// semantics — and unlike the boxed v1 backing, it actually coerces the write to the element
|
|
949
|
+
// type. Out-of-range index zero-fills, matching the boxed grow-with-Null behaviour.
|
|
950
|
+
Value::NumberArray(arr) => {
|
|
951
|
+
let index = match idx {
|
|
952
|
+
Value::Number(n) => *n as usize,
|
|
953
|
+
_ => panic!("Array index must be number"),
|
|
954
|
+
};
|
|
955
|
+
let mut arr_mut = arr.borrow_mut();
|
|
956
|
+
while arr_mut.len() <= index {
|
|
957
|
+
arr_mut.push(0.0);
|
|
958
|
+
}
|
|
959
|
+
arr_mut[index] = val.as_number().unwrap_or(f64::NAN);
|
|
960
|
+
val
|
|
961
|
+
}
|
|
732
962
|
Value::Object(_) => {
|
|
733
963
|
tishlang_core::object_set(obj, idx, val.clone()).expect("object set");
|
|
734
964
|
val
|
|
@@ -742,7 +972,22 @@ pub fn in_operator(key: &Value, obj: &Value) -> Value {
|
|
|
742
972
|
Value::Object(_) => Value::Bool(tishlang_core::object_has(obj, key)),
|
|
743
973
|
Value::Array(arr) => {
|
|
744
974
|
let key_str: Arc<str> = match key {
|
|
745
|
-
Value::String(s) => Arc::
|
|
975
|
+
Value::String(s) => Arc::from(s.as_str()),
|
|
976
|
+
Value::Number(n) => n.to_string().into(),
|
|
977
|
+
_ => return Value::Bool(false),
|
|
978
|
+
};
|
|
979
|
+
let result = key_str.as_ref() == "length"
|
|
980
|
+
|| key_str
|
|
981
|
+
.parse::<usize>()
|
|
982
|
+
.ok()
|
|
983
|
+
.map(|i| i < arr.borrow().len())
|
|
984
|
+
.unwrap_or(false);
|
|
985
|
+
Value::Bool(result)
|
|
986
|
+
}
|
|
987
|
+
// Packed `Float64Array` (`TISH_PACKED_ARRAYS`); same key set as the boxed `Array` arm.
|
|
988
|
+
Value::NumberArray(arr) => {
|
|
989
|
+
let key_str: Arc<str> = match key {
|
|
990
|
+
Value::String(s) => Arc::from(s.as_str()),
|
|
746
991
|
Value::Number(n) => n.to_string().into(),
|
|
747
992
|
_ => return Value::Bool(false),
|
|
748
993
|
};
|
|
@@ -809,6 +1054,13 @@ mod native_promise;
|
|
|
809
1054
|
#[cfg(feature = "ws")]
|
|
810
1055
|
mod ws;
|
|
811
1056
|
|
|
1057
|
+
#[cfg(feature = "tty")]
|
|
1058
|
+
pub mod tty;
|
|
1059
|
+
#[cfg(feature = "tty")]
|
|
1060
|
+
pub use tty::{
|
|
1061
|
+
tty_enter_alt_screen, tty_is_tty, tty_leave_alt_screen, tty_read, tty_read_line, tty_set_raw_mode, tty_size,
|
|
1062
|
+
};
|
|
1063
|
+
|
|
812
1064
|
#[cfg(feature = "ws")]
|
|
813
1065
|
pub use ws::{
|
|
814
1066
|
web_socket_client, web_socket_server_accept, web_socket_server_construct,
|
|
@@ -868,7 +1120,7 @@ pub fn http_serve_per_worker(
|
|
|
868
1120
|
};
|
|
869
1121
|
let factory: tishlang_core::NativeFn = factory;
|
|
870
1122
|
http::serve_per_worker(args, move |worker_id| {
|
|
871
|
-
let handler_val = factory(&[Value::Number(worker_id as f64)]);
|
|
1123
|
+
let handler_val = factory.call(&[Value::Number(worker_id as f64)]);
|
|
872
1124
|
match handler_val {
|
|
873
1125
|
Value::Function(f) => f,
|
|
874
1126
|
_ => panic!(
|
|
@@ -885,7 +1137,10 @@ pub use timers::{
|
|
|
885
1137
|
};
|
|
886
1138
|
|
|
887
1139
|
#[cfg(any(feature = "http", feature = "promise"))]
|
|
888
|
-
pub use promise::{
|
|
1140
|
+
pub use promise::{
|
|
1141
|
+
await_promise, await_promise_throw, promise_instance_catch, promise_instance_then,
|
|
1142
|
+
promise_object, promise_spawn as promise_spawn_value,
|
|
1143
|
+
};
|
|
889
1144
|
|
|
890
1145
|
#[cfg(feature = "http")]
|
|
891
1146
|
pub use native_promise::{fetch_all_promise, fetch_async_promise, fetch_promise};
|
|
@@ -1040,7 +1295,7 @@ pub fn string_split_regex(s: &Value, separator: &Value, limit: Option<usize>) ->
|
|
|
1040
1295
|
}
|
|
1041
1296
|
Value::String(sep) => {
|
|
1042
1297
|
let parts: Vec<Value> = input
|
|
1043
|
-
.splitn(max, sep.
|
|
1298
|
+
.splitn(max, sep.as_str())
|
|
1044
1299
|
.map(|s| Value::String(s.into()))
|
|
1045
1300
|
.collect();
|
|
1046
1301
|
Value::Array(VmRef::new(parts))
|
|
@@ -1134,7 +1389,7 @@ fn string_replace_regex_or_callback(s: &Value, search: &Value, replacement: &Val
|
|
|
1134
1389
|
args.push(Value::Number(char_index as f64));
|
|
1135
1390
|
args.push(Value::String(input.into()));
|
|
1136
1391
|
|
|
1137
|
-
let repl_val = cb(&args);
|
|
1392
|
+
let repl_val = cb.call(&args);
|
|
1138
1393
|
let repl_str = repl_val.to_display_string();
|
|
1139
1394
|
result.push_str(&input[last_end..byte_start]);
|
|
1140
1395
|
result.push_str(&repl_str);
|