@tishlang/tish-format 2.39.0 → 3.0.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/Cargo.toml +4 -1
- package/bin/tish-format +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +21 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/tish.d.tish +4 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +5 -1
- package/crates/tish/tests/regr_typed_externs.rs +59 -0
- package/crates/tish/tests/scope_merge.rs +79 -0
- package/crates/tish_build_utils/src/lib.rs +27 -1
- package/crates/tish_builtins/Cargo.toml +14 -6
- package/crates/tish_builtins/src/array.rs +130 -54
- package/crates/tish_builtins/src/collections.rs +9 -5
- package/crates/tish_builtins/src/construct.rs +7 -1
- package/crates/tish_builtins/src/date.rs +15 -1
- package/crates/tish_builtins/src/globals.rs +65 -3
- package/crates/tish_builtins/src/helpers.rs +7 -3
- package/crates/tish_builtins/src/iterator.rs +5 -1
- package/crates/tish_builtins/src/lib.rs +3 -0
- package/crates/tish_builtins/src/math.rs +14 -0
- package/crates/tish_builtins/src/number.rs +72 -6
- package/crates/tish_builtins/src/object.rs +6 -1
- package/crates/tish_builtins/src/string.rs +13 -2
- package/crates/tish_builtins/src/symbol.rs +17 -17
- package/crates/tish_builtins/src/typedarrays.rs +9 -2
- package/crates/tish_bytecode/src/compiler.rs +76 -2
- package/crates/tish_bytecode/src/lib.rs +1 -1
- package/crates/tish_bytecode/src/opcode.rs +77 -2
- package/crates/tish_compile/src/codegen.rs +2154 -225
- package/crates/tish_compile/src/infer.rs +37 -41
- package/crates/tish_compile/src/lib.rs +13 -1
- package/crates/tish_compile/src/platform_resolve.rs +361 -0
- package/crates/tish_compile/src/resolve.rs +195 -11
- package/crates/tish_compile/src/schemes.rs +333 -0
- package/crates/tish_compile/src/types.rs +205 -34
- package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
- package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
- package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
- package/crates/tish_compile/tests/regr_556_param_name_collision.rs +40 -0
- package/crates/tish_compile/tests/regr_558_struct_array_write.rs +65 -0
- package/crates/tish_compile/tests/regr_558b_struct_field_read.rs +21 -0
- package/crates/tish_compile/tests/regr_558c_wrapped_struct_array.rs +64 -0
- package/crates/tish_compile/tests/regr_558d_native_vec_mutators.rs +156 -0
- package/crates/tish_compile/tests/regr_562_wrapped_struct_array_push.rs +50 -0
- package/crates/tish_compile/tests/regr_fixed_native_coercions.rs +69 -0
- package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
- package/crates/tish_compile/tests/regr_gba_struct_reflection.rs +106 -0
- package/crates/tish_compile/tests/regression/name_collision_lib.tish +6 -0
- package/crates/tish_compile/tests/regression/name_collision_main.tish +13 -0
- package/crates/tish_compile/tests/regression/struct_array_write.tish +22 -0
- package/crates/tish_compile/tests/regression/struct_field_return_read.tish +10 -0
- package/crates/tish_compiler_wasm/Cargo.toml +1 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
- package/crates/tish_core/Cargo.toml +39 -4
- package/crates/tish_core/src/compat.rs +506 -0
- package/crates/tish_core/src/console_style.rs +23 -1
- package/crates/tish_core/src/json.rs +42 -10
- package/crates/tish_core/src/lib.rs +121 -25
- package/crates/tish_core/src/macros.rs +1 -1
- package/crates/tish_core/src/shape.rs +4 -2
- package/crates/tish_core/src/uri.rs +8 -1
- package/crates/tish_core/src/value.rs +305 -54
- package/crates/tish_core/src/vmref.rs +17 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +90 -3
- package/crates/tish_eval/src/value.rs +8 -0
- package/crates/tish_eval/src/value_convert.rs +316 -21
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/crates/tish_lsp/Cargo.toml +2 -0
- package/crates/tish_lsp/src/import_goto.rs +41 -14
- package/crates/tish_lsp/src/main.rs +18 -0
- package/crates/tish_native/src/build.rs +246 -0
- package/crates/tish_native/src/config.rs +20 -0
- package/crates/tish_native/src/lib.rs +16 -6
- package/crates/tish_parser/src/lib.rs +28 -0
- package/crates/tish_parser/src/parser.rs +25 -4
- package/crates/tish_runtime/src/lib.rs +55 -9
- package/crates/tish_runtime_gba/Cargo.lock +837 -0
- package/crates/tish_runtime_gba/Cargo.toml +16 -0
- package/crates/tish_runtime_gba/src/gba.rs +211 -0
- package/crates/tish_runtime_gba/src/lib.rs +804 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +583 -173
- package/justfile +9 -0
- 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
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
//! Array builtin methods.
|
|
2
2
|
|
|
3
|
+
#[cfg(feature = "portable")]
|
|
4
|
+
#[allow(unused_imports)]
|
|
5
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
6
|
+
|
|
3
7
|
use crate::helpers::normalize_index;
|
|
4
8
|
use tishlang_core::Value;
|
|
5
9
|
use tishlang_core::VmRef;
|
|
@@ -18,7 +22,7 @@ pub fn from_vec(v: Vec<Value>) -> Value {
|
|
|
18
22
|
pub fn snapshot_values(arr: &Value) -> Vec<Value> {
|
|
19
23
|
match arr {
|
|
20
24
|
Value::Array(a) => a.borrow().clone(),
|
|
21
|
-
Value::NumberArray(a) => a.borrow().
|
|
25
|
+
Value::NumberArray(a) => a.borrow().to_values(),
|
|
22
26
|
_ => Vec::new(),
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -30,19 +34,24 @@ pub fn snapshot_values(arr: &Value) -> Vec<Value> {
|
|
|
30
34
|
/// unboxed f64 fast path for it too. The `None` result drives the fused lowering back to its boxed
|
|
31
35
|
/// loop, so any non-numeric element is handled with identical `Value` semantics.
|
|
32
36
|
pub fn as_f64_snapshot(arr: &Value) -> Option<Vec<f64>> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
match v {
|
|
40
|
-
Value::Number(n) => out.push(*n),
|
|
41
|
-
_ => return None,
|
|
42
|
-
}
|
|
37
|
+
let extract = |vals: &[Value]| -> Option<Vec<f64>> {
|
|
38
|
+
let mut out: Vec<f64> = Vec::with_capacity(vals.len());
|
|
39
|
+
for v in vals {
|
|
40
|
+
match v {
|
|
41
|
+
Value::Number(n) => out.push(*n),
|
|
42
|
+
_ => return None,
|
|
43
43
|
}
|
|
44
|
-
Some(out)
|
|
45
44
|
}
|
|
45
|
+
Some(out)
|
|
46
|
+
};
|
|
47
|
+
match arr {
|
|
48
|
+
// Still packed: clone the f64s directly. Deopted: extract from the boxed values (None if
|
|
49
|
+
// any is non-numeric — the caller then uses the boxed path).
|
|
50
|
+
Value::NumberArray(a) => match a.borrow().as_packed() {
|
|
51
|
+
Some(v) => Some(v.clone()),
|
|
52
|
+
None => extract(a.borrow().as_boxed().map(|v| v.as_slice()).unwrap_or(&[])),
|
|
53
|
+
},
|
|
54
|
+
Value::Array(a) => extract(&a.borrow()),
|
|
46
55
|
_ => None,
|
|
47
56
|
}
|
|
48
57
|
}
|
|
@@ -60,10 +69,10 @@ pub fn len(arr: &Value) -> Option<usize> {
|
|
|
60
69
|
/// can use this deopt helper rather than changing every `if let Value::Array` branch.
|
|
61
70
|
/// Returns the original value unchanged for anything that isn't a `NumberArray`.
|
|
62
71
|
#[inline]
|
|
63
|
-
fn as_boxed_array(arr: &Value) ->
|
|
72
|
+
fn as_boxed_array(arr: &Value) -> alloc::borrow::Cow<'_, Value> {
|
|
64
73
|
match arr {
|
|
65
|
-
Value::NumberArray(na) =>
|
|
66
|
-
other =>
|
|
74
|
+
Value::NumberArray(na) => alloc::borrow::Cow::Owned(Value::materialize_number_array(na)),
|
|
75
|
+
other => alloc::borrow::Cow::Borrowed(other),
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -81,7 +90,9 @@ fn packed_snapshot<'c>(
|
|
|
81
90
|
callback: &'c Value,
|
|
82
91
|
) -> Option<(Vec<f64>, &'c tishlang_core::NativeFn)> {
|
|
83
92
|
match (arr, callback) {
|
|
84
|
-
(Value::NumberArray(na), Value::Function(cb)) =>
|
|
93
|
+
(Value::NumberArray(na), Value::Function(cb)) => {
|
|
94
|
+
na.borrow().as_packed().map(|v| (v.clone(), cb))
|
|
95
|
+
}
|
|
85
96
|
_ => None,
|
|
86
97
|
}
|
|
87
98
|
}
|
|
@@ -282,8 +293,20 @@ pub fn shuffle(arr: &Value) -> Value {
|
|
|
282
293
|
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
283
294
|
if let Value::Array(arr) = arr {
|
|
284
295
|
let mut v = arr.borrow().clone();
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
#[cfg(not(feature = "portable"))]
|
|
297
|
+
{
|
|
298
|
+
use rand::seq::SliceRandom;
|
|
299
|
+
v.shuffle(&mut rand::rng());
|
|
300
|
+
}
|
|
301
|
+
// Fisher-Yates with the runtime RNG hook (no `rand` on GBA).
|
|
302
|
+
#[cfg(feature = "portable")]
|
|
303
|
+
{
|
|
304
|
+
let n = v.len();
|
|
305
|
+
for i in (1..n).rev() {
|
|
306
|
+
let j = (tishlang_core::next_u64() % (i as u64 + 1)) as usize;
|
|
307
|
+
v.swap(i, j);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
287
310
|
Value::Array(VmRef::new(v))
|
|
288
311
|
} else {
|
|
289
312
|
Value::Null
|
|
@@ -404,10 +427,13 @@ pub fn concat(arr: &Value, args: &[Value]) -> Value {
|
|
|
404
427
|
if let Value::Array(arr) = arr {
|
|
405
428
|
let mut result = arr.borrow().clone();
|
|
406
429
|
for v in args {
|
|
407
|
-
|
|
408
|
-
result.extend(other.borrow().iter().cloned())
|
|
409
|
-
|
|
410
|
-
|
|
430
|
+
match v {
|
|
431
|
+
Value::Array(other) => result.extend(other.borrow().iter().cloned()),
|
|
432
|
+
// A packed `NumberArray` arg spreads its numbers, same as a boxed array — under
|
|
433
|
+
// TISH_PACKED_ARRAYS=1 a numeric literal `[3,4]` is a NumberArray, so
|
|
434
|
+
// `[1,2].concat([3,4])` must flatten to `[1,2,3,4]`, not push the array whole.
|
|
435
|
+
Value::NumberArray(other) => result.extend(other.borrow().to_values()),
|
|
436
|
+
_ => result.push(v.clone()),
|
|
411
437
|
}
|
|
412
438
|
}
|
|
413
439
|
Value::Array(VmRef::new(result))
|
|
@@ -421,9 +447,19 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
|
|
|
421
447
|
fn flatten(arr: &[Value], depth: i32, result: &mut Vec<Value>) {
|
|
422
448
|
for v in arr {
|
|
423
449
|
if depth > 0 {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
450
|
+
match v {
|
|
451
|
+
Value::Array(inner) => {
|
|
452
|
+
flatten(&inner.borrow(), depth - 1, result);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
// A packed `NumberArray` inner flattens like a boxed array (its numbers are the
|
|
456
|
+
// flattened elements) — `[[1,2],[3,4]].flat()` where the inners packed must not
|
|
457
|
+
// leave a `NumberArray` sitting as a scalar element.
|
|
458
|
+
Value::NumberArray(inner) => {
|
|
459
|
+
result.extend(inner.borrow().to_values());
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
_ => {}
|
|
427
463
|
}
|
|
428
464
|
}
|
|
429
465
|
result.push(v.clone());
|
|
@@ -871,7 +907,7 @@ pub fn flat_map(arr: &Value, callback: &Value) -> Value {
|
|
|
871
907
|
|
|
872
908
|
fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
|
|
873
909
|
where
|
|
874
|
-
F: FnMut(&Value, &Value) ->
|
|
910
|
+
F: FnMut(&Value, &Value) -> core::cmp::Ordering,
|
|
875
911
|
{
|
|
876
912
|
if let Value::Array(arr) = arr {
|
|
877
913
|
arr.borrow_mut().sort_by(cmp);
|
|
@@ -895,7 +931,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
895
931
|
// Mutex under send-values and self-deadlock the worker (or panic under RefCell) — the exact
|
|
896
932
|
// reentrant-lock class fixed for the captured-cell path in #218/#338. `mem::take` leaves the
|
|
897
933
|
// array empty for the duration of the sort; the final result is written back below.
|
|
898
|
-
let mut elements: Vec<Value> =
|
|
934
|
+
let mut elements: Vec<Value> = core::mem::take(&mut *arr.borrow_mut());
|
|
899
935
|
let len = elements.len();
|
|
900
936
|
let mut indices: Vec<usize> = (0..len).collect();
|
|
901
937
|
let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
|
|
@@ -904,14 +940,14 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
904
940
|
// #303: once the comparator has thrown, stop invoking it — return Equal so the sort can
|
|
905
941
|
// unwind. Avoids extra comparator calls (and their side effects) after the throw.
|
|
906
942
|
if tishlang_core::has_pending_throw() {
|
|
907
|
-
return
|
|
943
|
+
return core::cmp::Ordering::Equal;
|
|
908
944
|
}
|
|
909
945
|
args_buf[0] = elements[a].clone();
|
|
910
946
|
args_buf[1] = elements[b].clone();
|
|
911
947
|
match cmp_fn.call(&args_buf) {
|
|
912
|
-
Value::Number(n) if n < 0.0 =>
|
|
913
|
-
Value::Number(n) if n > 0.0 =>
|
|
914
|
-
_ =>
|
|
948
|
+
Value::Number(n) if n < 0.0 => core::cmp::Ordering::Less,
|
|
949
|
+
Value::Number(n) if n > 0.0 => core::cmp::Ordering::Greater,
|
|
950
|
+
_ => core::cmp::Ordering::Equal,
|
|
915
951
|
}
|
|
916
952
|
});
|
|
917
953
|
|
|
@@ -923,7 +959,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
923
959
|
} else {
|
|
924
960
|
let sorted: Vec<Value> = indices
|
|
925
961
|
.into_iter()
|
|
926
|
-
.map(|i|
|
|
962
|
+
.map(|i| core::mem::replace(&mut elements[i], Value::Null))
|
|
927
963
|
.collect();
|
|
928
964
|
*arr.borrow_mut() = sorted;
|
|
929
965
|
}
|
|
@@ -933,12 +969,12 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
933
969
|
}
|
|
934
970
|
}
|
|
935
971
|
|
|
936
|
-
fn num_cmp(a: &Value, b: &Value, asc: bool) ->
|
|
972
|
+
fn num_cmp(a: &Value, b: &Value, asc: bool) -> core::cmp::Ordering {
|
|
937
973
|
let (na, nb) = match (a, b) {
|
|
938
974
|
(Value::Number(a), Value::Number(b)) => (*a, *b),
|
|
939
975
|
_ => (f64::NAN, f64::NAN),
|
|
940
976
|
};
|
|
941
|
-
let cmp = na.partial_cmp(&nb).unwrap_or(
|
|
977
|
+
let cmp = na.partial_cmp(&nb).unwrap_or(core::cmp::Ordering::Equal);
|
|
942
978
|
if asc {
|
|
943
979
|
cmp
|
|
944
980
|
} else {
|
|
@@ -961,13 +997,15 @@ pub fn sort_numeric_desc(arr: &Value) -> Value {
|
|
|
961
997
|
fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
962
998
|
// NumberArray fast path: sort the Vec<f64> directly — no unbox pass, no rebox.
|
|
963
999
|
if let Value::NumberArray(a) = arr {
|
|
964
|
-
|
|
965
|
-
if
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1000
|
+
// Fast path only while still packed; a deopted numeric array sorts via the boxed path below.
|
|
1001
|
+
if let Some(nums) = a.borrow_mut().as_packed_mut() {
|
|
1002
|
+
if asc {
|
|
1003
|
+
nums.sort_unstable_by(|x, y| x.partial_cmp(y).unwrap_or(core::cmp::Ordering::Equal));
|
|
1004
|
+
} else {
|
|
1005
|
+
nums.sort_unstable_by(|x, y| y.partial_cmp(x).unwrap_or(core::cmp::Ordering::Equal));
|
|
1006
|
+
}
|
|
1007
|
+
return Value::NumberArray(a.clone());
|
|
969
1008
|
}
|
|
970
|
-
return Value::NumberArray(a.clone());
|
|
971
1009
|
}
|
|
972
1010
|
if let Value::Array(a) = arr {
|
|
973
1011
|
{
|
|
@@ -982,11 +1020,11 @@ fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
|
982
1020
|
.collect();
|
|
983
1021
|
if asc {
|
|
984
1022
|
nums.sort_unstable_by(|x, y| {
|
|
985
|
-
x.partial_cmp(y).unwrap_or(
|
|
1023
|
+
x.partial_cmp(y).unwrap_or(core::cmp::Ordering::Equal)
|
|
986
1024
|
});
|
|
987
1025
|
} else {
|
|
988
1026
|
nums.sort_unstable_by(|x, y| {
|
|
989
|
-
y.partial_cmp(x).unwrap_or(
|
|
1027
|
+
y.partial_cmp(x).unwrap_or(core::cmp::Ordering::Equal)
|
|
990
1028
|
});
|
|
991
1029
|
}
|
|
992
1030
|
for (slot, n) in g.iter_mut().zip(nums) {
|
|
@@ -1004,11 +1042,11 @@ fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
|
1004
1042
|
|
|
1005
1043
|
/// Sort array of objects by numeric property: arr.sort((a,b)=>a.prop-b.prop)
|
|
1006
1044
|
pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
|
|
1007
|
-
let prop_arc =
|
|
1045
|
+
let prop_arc = tishlang_core::Arc::from(prop);
|
|
1008
1046
|
sort_by_impl(arr, move |a, b| {
|
|
1009
1047
|
let na = get_prop_number(a, &prop_arc);
|
|
1010
1048
|
let nb = get_prop_number(b, &prop_arc);
|
|
1011
|
-
let cmp = na.partial_cmp(&nb).unwrap_or(
|
|
1049
|
+
let cmp = na.partial_cmp(&nb).unwrap_or(core::cmp::Ordering::Equal);
|
|
1012
1050
|
if asc {
|
|
1013
1051
|
cmp
|
|
1014
1052
|
} else {
|
|
@@ -1017,7 +1055,7 @@ pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
|
|
|
1017
1055
|
})
|
|
1018
1056
|
}
|
|
1019
1057
|
|
|
1020
|
-
fn get_prop_number(v: &Value, prop: &
|
|
1058
|
+
fn get_prop_number(v: &Value, prop: &tishlang_core::Arc<str>) -> f64 {
|
|
1021
1059
|
match v {
|
|
1022
1060
|
Value::Object(o) => o
|
|
1023
1061
|
.borrow()
|
|
@@ -1097,19 +1135,19 @@ pub fn sort_by_keys(arr: &Value, keys: &[(&[&str], bool)]) -> Value {
|
|
|
1097
1135
|
for (col, (_, asc)) in keys.iter().enumerate() {
|
|
1098
1136
|
let ord = ka[col]
|
|
1099
1137
|
.partial_cmp(&kb[col])
|
|
1100
|
-
.unwrap_or(
|
|
1138
|
+
.unwrap_or(core::cmp::Ordering::Equal);
|
|
1101
1139
|
let ord = if *asc { ord } else { ord.reverse() };
|
|
1102
|
-
if ord !=
|
|
1140
|
+
if ord != core::cmp::Ordering::Equal {
|
|
1103
1141
|
return ord;
|
|
1104
1142
|
}
|
|
1105
1143
|
}
|
|
1106
|
-
|
|
1144
|
+
core::cmp::Ordering::Equal
|
|
1107
1145
|
});
|
|
1108
1146
|
// Undecorate: apply the permutation in place.
|
|
1109
|
-
let mut elements: Vec<Value> =
|
|
1147
|
+
let mut elements: Vec<Value> = core::mem::take(&mut *g);
|
|
1110
1148
|
*g = decorated
|
|
1111
1149
|
.into_iter()
|
|
1112
|
-
.map(|(i, _)|
|
|
1150
|
+
.map(|(i, _)| core::mem::replace(&mut elements[i], Value::Null))
|
|
1113
1151
|
.collect();
|
|
1114
1152
|
drop(g);
|
|
1115
1153
|
Value::Array(a.clone())
|
|
@@ -1125,15 +1163,15 @@ mod packed_hof_tests {
|
|
|
1125
1163
|
//! on it. These run with packing semantics directly (the helpers don't read the env flag; a
|
|
1126
1164
|
//! `NumberArray` value is enough to take the fast path).
|
|
1127
1165
|
use super::*;
|
|
1128
|
-
use tishlang_core::Value;
|
|
1166
|
+
use tishlang_core::{NumArrayBacking, Value};
|
|
1129
1167
|
|
|
1130
1168
|
fn na(xs: &[f64]) -> Value {
|
|
1131
|
-
Value::NumberArray(VmRef::new(xs.to_vec()))
|
|
1169
|
+
Value::NumberArray(VmRef::new(NumArrayBacking::Packed(xs.to_vec())))
|
|
1132
1170
|
}
|
|
1133
1171
|
fn nums(v: &Value) -> Vec<f64> {
|
|
1134
1172
|
match v {
|
|
1135
1173
|
Value::Array(a) => a.borrow().iter().map(|e| e.as_number().unwrap_or(f64::NAN)).collect(),
|
|
1136
|
-
Value::NumberArray(a) => a.borrow().
|
|
1174
|
+
Value::NumberArray(a) => a.borrow().to_values().iter().map(|e| e.as_number().unwrap_or(f64::NAN)).collect(),
|
|
1137
1175
|
_ => vec![],
|
|
1138
1176
|
}
|
|
1139
1177
|
}
|
|
@@ -1317,7 +1355,7 @@ mod sort_by_keys_tests {
|
|
|
1317
1355
|
//! EXACT same order as running the equivalent boxed comparator through
|
|
1318
1356
|
//! `sort_with_comparator`, since cross-backend parity depends on it.
|
|
1319
1357
|
use super::*;
|
|
1320
|
-
use
|
|
1358
|
+
use tishlang_core::Arc;
|
|
1321
1359
|
use tishlang_core::Value;
|
|
1322
1360
|
|
|
1323
1361
|
fn rec(key: f64, idx: f64) -> Value {
|
|
@@ -1533,4 +1571,42 @@ mod reentrancy_tests_382 {
|
|
|
1533
1571
|
_ => panic!("sort should return an array"),
|
|
1534
1572
|
}
|
|
1535
1573
|
}
|
|
1574
|
+
|
|
1575
|
+
fn nums(v: &Value) -> Vec<f64> {
|
|
1576
|
+
match v {
|
|
1577
|
+
Value::Array(a) => a.borrow().iter().filter_map(|e| e.as_number()).collect(),
|
|
1578
|
+
_ => panic!("expected boxed array result"),
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/// #505: a packed `NumberArray` concat ARG must spread its numbers, not push the array whole.
|
|
1583
|
+
#[test]
|
|
1584
|
+
fn concat_spreads_packed_number_array_arg() {
|
|
1585
|
+
let recv = Value::Array(VmRef::new(vec![Value::Number(1.0), Value::Number(2.0)]));
|
|
1586
|
+
let packed = Value::number_array(vec![3.0, 4.0]);
|
|
1587
|
+
let out = concat(&recv, &[packed]);
|
|
1588
|
+
assert_eq!(nums(&out), vec![1.0, 2.0, 3.0, 4.0]);
|
|
1589
|
+
// Mixed with a boxed array + a scalar, order preserved.
|
|
1590
|
+
let boxed = Value::Array(VmRef::new(vec![Value::Number(5.0)]));
|
|
1591
|
+
let out2 = concat(&recv, &[boxed, Value::Number(9.0)]);
|
|
1592
|
+
assert_eq!(nums(&out2), vec![1.0, 2.0, 5.0, 9.0]);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/// #507: a packed `NumberArray` INNER element must flatten like a boxed array.
|
|
1596
|
+
#[test]
|
|
1597
|
+
fn flat_flattens_packed_number_array_inner() {
|
|
1598
|
+
let outer = Value::Array(VmRef::new(vec![
|
|
1599
|
+
Value::number_array(vec![1.0, 2.0]),
|
|
1600
|
+
Value::Number(3.0),
|
|
1601
|
+
Value::Array(VmRef::new(vec![Value::Number(4.0)])),
|
|
1602
|
+
]));
|
|
1603
|
+
let out = flat(&outer, &Value::Number(1.0));
|
|
1604
|
+
assert_eq!(nums(&out), vec![1.0, 2.0, 3.0, 4.0]);
|
|
1605
|
+
// depth 0 keeps the packed inner as-is (not flattened) — matches boxed-array behavior.
|
|
1606
|
+
let out0 = flat(&outer, &Value::Number(0.0));
|
|
1607
|
+
match out0 {
|
|
1608
|
+
Value::Array(a) => assert_eq!(a.borrow().len(), 3, "depth 0 flattens nothing"),
|
|
1609
|
+
_ => panic!("expected array"),
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1536
1612
|
}
|
|
@@ -23,9 +23,13 @@
|
|
|
23
23
|
//! - Keys/values use **SameValueZero** equality (NaN equals NaN; `+0`/`-0` are the same; objects by
|
|
24
24
|
//! identity).
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
use
|
|
26
|
+
#[cfg(feature = "portable")]
|
|
27
|
+
#[allow(unused_imports)]
|
|
28
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
29
|
+
|
|
30
|
+
use core::any::Any;
|
|
31
|
+
use core::hash::{Hash, Hasher};
|
|
32
|
+
use tishlang_core::Arc;
|
|
29
33
|
|
|
30
34
|
use indexmap::IndexMap;
|
|
31
35
|
use tishlang_core::{NativeFn, ObjectMap, TishOpaque, Value, VmRef};
|
|
@@ -120,7 +124,7 @@ impl indexmap::Equivalent<Key> for KeyRef<'_> {
|
|
|
120
124
|
/// stores `Value::Null` values and iterates its keys; a `Map` stores the mapped value. Uses `ahash`
|
|
121
125
|
/// (the same fast hasher `tish_core`'s `PropMap` uses) rather than the default SipHash — iteration
|
|
122
126
|
/// order is insertion order regardless of the hasher, so this is a pure constant-factor speedup.
|
|
123
|
-
type Store = VmRef<IndexMap<Key, Value,
|
|
127
|
+
type Store = VmRef<IndexMap<Key, Value, tishlang_core::RandomState>>;
|
|
124
128
|
|
|
125
129
|
/// Opaque whose length reports a collection's live element count. Stored under [`SIZE_SLOT`] on every
|
|
126
130
|
/// `Set`/`Map` instance.
|
|
@@ -226,7 +230,7 @@ fn same_value_zero(a: &Value, b: &Value) -> bool {
|
|
|
226
230
|
fn iter_elements(v: &Value) -> Vec<Value> {
|
|
227
231
|
match v {
|
|
228
232
|
Value::Array(a) => a.borrow().iter().cloned().collect(),
|
|
229
|
-
Value::NumberArray(a) => a.borrow().
|
|
233
|
+
Value::NumberArray(a) => a.borrow().to_values(),
|
|
230
234
|
_ => Vec::new(),
|
|
231
235
|
}
|
|
232
236
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
//! `new` lowering for non-JS targets: `construct(callee, args)` approximates JS `[[Construct]]`.
|
|
2
2
|
//! Browser-exact behavior remains on `tish build --target js`.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
#[cfg(feature = "portable")]
|
|
5
|
+
#[allow(unused_imports)]
|
|
6
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
7
|
+
#[cfg(feature = "portable")]
|
|
8
|
+
use tishlang_core::FloatExt;
|
|
9
|
+
|
|
10
|
+
use tishlang_core::Arc;
|
|
5
11
|
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
6
12
|
|
|
7
13
|
const CONSTRUCT: &str = "__construct";
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
//! is no timezone database in the core builtins (keeps them lean + wasm-friendly). `getTime`,
|
|
12
12
|
//! `valueOf`, the `getUTC*` family and `toISOString` are therefore fully deterministic everywhere.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
#[cfg(feature = "portable")]
|
|
15
|
+
#[allow(unused_imports)]
|
|
16
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
17
|
+
#[cfg(feature = "portable")]
|
|
18
|
+
use tishlang_core::FloatExt;
|
|
19
|
+
|
|
20
|
+
use tishlang_core::Arc;
|
|
15
21
|
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
16
22
|
|
|
17
23
|
const CONSTRUCT: &str = "__construct";
|
|
@@ -23,6 +29,7 @@ const MONTHS: [&str; 12] = [
|
|
|
23
29
|
];
|
|
24
30
|
|
|
25
31
|
/// Current wall-clock time as epoch milliseconds.
|
|
32
|
+
#[cfg(not(feature = "portable"))]
|
|
26
33
|
fn now_ms() -> f64 {
|
|
27
34
|
std::time::SystemTime::now()
|
|
28
35
|
.duration_since(std::time::UNIX_EPOCH)
|
|
@@ -30,6 +37,13 @@ fn now_ms() -> f64 {
|
|
|
30
37
|
.unwrap_or(0.0)
|
|
31
38
|
}
|
|
32
39
|
|
|
40
|
+
/// No OS clock on GBA: read the runtime-installed clock hook (defaults to 0 until
|
|
41
|
+
/// the facade wires up an agb timer). See `tishlang_core::install_clock`.
|
|
42
|
+
#[cfg(feature = "portable")]
|
|
43
|
+
fn now_ms() -> f64 {
|
|
44
|
+
tishlang_core::now_ms()
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
/// A `(method name, field extractor)` pair for the numeric `Date` getters. The explicit
|
|
34
48
|
/// `fn(&Civil) -> i64` coerces the (non-capturing) extractor closures to a single fn-pointer type so
|
|
35
49
|
/// they can live in one array.
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
//! Used by both tishlang_vm (bytecode) and tishlang_runtime (compiled). Keeps tishlang_vm
|
|
4
4
|
//! independent of tishlang_runtime.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
#[cfg(feature = "portable")]
|
|
7
|
+
#[allow(unused_imports)]
|
|
8
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
9
|
+
#[cfg(feature = "portable")]
|
|
10
|
+
use tishlang_core::FloatExt;
|
|
11
|
+
|
|
12
|
+
use tishlang_core::Arc;
|
|
7
13
|
use tishlang_core::VmRef;
|
|
8
14
|
use tishlang_core::{percent_decode, percent_decode_component, percent_encode, percent_encode_component, ObjectMap, Value};
|
|
9
15
|
|
|
@@ -79,7 +85,7 @@ pub fn array_from(args: &[Value]) -> Value {
|
|
|
79
85
|
let source = args.first().cloned().unwrap_or(Value::Null);
|
|
80
86
|
let items: Vec<Value> = match &source {
|
|
81
87
|
Value::Array(a) => a.borrow().clone(),
|
|
82
|
-
Value::NumberArray(a) => a.borrow().
|
|
88
|
+
Value::NumberArray(a) => a.borrow().to_values(),
|
|
83
89
|
// `drain_iterator` covers String (chars), Set/Map, and any iterator object.
|
|
84
90
|
other => {
|
|
85
91
|
if let Some(d) = tishlang_core::drain_iterator(other) {
|
|
@@ -296,6 +302,25 @@ pub fn string_from_char_code(args: &[Value]) -> Value {
|
|
|
296
302
|
Value::String(s.into())
|
|
297
303
|
}
|
|
298
304
|
|
|
305
|
+
/// Object.freeze(obj) — mark the object frozen so later property writes throw a catchable
|
|
306
|
+
/// TypeError (#437). Returns the same object; non-objects pass through unchanged (JS semantics).
|
|
307
|
+
pub fn object_freeze(args: &[Value]) -> Value {
|
|
308
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
309
|
+
obj.borrow_mut().frozen = true;
|
|
310
|
+
}
|
|
311
|
+
args.first().cloned().unwrap_or(Value::Null)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// Object.isFrozen(obj) — true if the object was frozen. Primitives are considered frozen (JS).
|
|
315
|
+
pub fn object_is_frozen(args: &[Value]) -> Value {
|
|
316
|
+
match args.first() {
|
|
317
|
+
Some(Value::Object(obj)) => Value::Bool(obj.borrow().frozen),
|
|
318
|
+
// Arrays are mutable and not freezable in this slice → not frozen; other primitives are.
|
|
319
|
+
Some(Value::Array(_)) => Value::Bool(false),
|
|
320
|
+
_ => Value::Bool(true),
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
299
324
|
/// Object.keys(obj)
|
|
300
325
|
pub fn object_keys(args: &[Value]) -> Value {
|
|
301
326
|
match args.first() {
|
|
@@ -319,12 +344,33 @@ pub fn object_keys(args: &[Value]) -> Value {
|
|
|
319
344
|
.collect();
|
|
320
345
|
Value::Array(VmRef::new(keys))
|
|
321
346
|
}
|
|
347
|
+
// A packed `NumberArray` has the same own enumerable keys (its index strings) — without
|
|
348
|
+
// this, `for (k in arr)` (lowered to `for-of Object.keys`) enumerated NOTHING under
|
|
349
|
+
// TISH_PACKED_ARRAYS=1. Mirrors the Array arm.
|
|
350
|
+
Some(Value::NumberArray(arr)) => {
|
|
351
|
+
let len = arr.borrow().len();
|
|
352
|
+
let keys: Vec<Value> = (0..len)
|
|
353
|
+
.map(|i| Value::String(tishlang_core::ArcStr::from(i.to_string().as_str())))
|
|
354
|
+
.collect();
|
|
355
|
+
Value::Array(VmRef::new(keys))
|
|
356
|
+
}
|
|
357
|
+
// A by-reference boxed typed struct (GBA/portable): materialise it to its ordered object and
|
|
358
|
+
// enumerate that, so `Object.keys(s)` / `for (k in s)` (lowered to `for-of Object.keys`) see
|
|
359
|
+
// the field names instead of nothing. (JSON/display already materialise; this closes the
|
|
360
|
+
// reflection paths so the boxed struct is enumerable exactly like the object it stands for.)
|
|
361
|
+
#[cfg(feature = "portable")]
|
|
362
|
+
Some(Value::Struct(s)) => object_keys(&[s.borrow().tish_to_object()]),
|
|
322
363
|
_ => Value::Array(VmRef::new(Vec::new())),
|
|
323
364
|
}
|
|
324
365
|
}
|
|
325
366
|
|
|
326
367
|
/// Object.values(obj)
|
|
327
368
|
pub fn object_values(args: &[Value]) -> Value {
|
|
369
|
+
// A boxed typed struct materialises to its ordered object first (see `object_keys`).
|
|
370
|
+
#[cfg(feature = "portable")]
|
|
371
|
+
if let Some(Value::Struct(s)) = args.first() {
|
|
372
|
+
return object_values(&[s.borrow().tish_to_object()]);
|
|
373
|
+
}
|
|
328
374
|
if let Some(Value::Object(obj)) = args.first() {
|
|
329
375
|
let obj_borrow = obj.borrow();
|
|
330
376
|
let values: Vec<Value> = obj_borrow.strings.values().cloned().collect();
|
|
@@ -336,6 +382,11 @@ pub fn object_values(args: &[Value]) -> Value {
|
|
|
336
382
|
|
|
337
383
|
/// Object.entries(obj)
|
|
338
384
|
pub fn object_entries(args: &[Value]) -> Value {
|
|
385
|
+
// A boxed typed struct materialises to its ordered object first (see `object_keys`).
|
|
386
|
+
#[cfg(feature = "portable")]
|
|
387
|
+
if let Some(Value::Struct(s)) = args.first() {
|
|
388
|
+
return object_entries(&[s.borrow().tish_to_object()]);
|
|
389
|
+
}
|
|
339
390
|
if let Some(Value::Object(obj)) = args.first() {
|
|
340
391
|
let obj_borrow = obj.borrow();
|
|
341
392
|
let entries: Vec<Value> = obj_borrow
|
|
@@ -372,6 +423,17 @@ pub fn object_assign(args: &[Value]) -> Value {
|
|
|
372
423
|
target_mut.strings.reserve(additional_capacity);
|
|
373
424
|
|
|
374
425
|
for source in args.iter().skip(1) {
|
|
426
|
+
// A boxed typed struct source materialises to its ordered object first (GBA/portable), so
|
|
427
|
+
// `Object.assign(t, s)` copies its fields instead of silently skipping it (see `object_keys`).
|
|
428
|
+
#[cfg(feature = "portable")]
|
|
429
|
+
let struct_obj;
|
|
430
|
+
#[cfg(feature = "portable")]
|
|
431
|
+
let source: &Value = if let Value::Struct(s) = source {
|
|
432
|
+
struct_obj = s.borrow().tish_to_object();
|
|
433
|
+
&struct_obj
|
|
434
|
+
} else {
|
|
435
|
+
source
|
|
436
|
+
};
|
|
375
437
|
if let Value::Object(src) = source {
|
|
376
438
|
let src_borrow = src.borrow();
|
|
377
439
|
for (k, v) in src_borrow.strings.iter() {
|
|
@@ -495,7 +557,7 @@ pub fn js_parse_float(s: &str) -> f64 {
|
|
|
495
557
|
pub fn object_from_entries(args: &[Value]) -> Value {
|
|
496
558
|
if let Some(Value::Array(entries)) = args.first() {
|
|
497
559
|
let entries_borrow = entries.borrow();
|
|
498
|
-
let mut obj: ObjectMap = ObjectMap::
|
|
560
|
+
let mut obj: ObjectMap = ObjectMap::with_capacity_and_hasher(entries_borrow.len(), Default::default());
|
|
499
561
|
|
|
500
562
|
for entry in entries_borrow.iter() {
|
|
501
563
|
if let Value::Array(pair) = entry {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
//! Common helper functions used across builtin implementations.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
#[cfg(feature = "portable")]
|
|
4
|
+
#[allow(unused_imports)]
|
|
5
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
6
|
+
|
|
7
|
+
use tishlang_core::Arc;
|
|
4
8
|
use tishlang_core::{ObjectMap, Value};
|
|
5
9
|
|
|
6
10
|
/// Normalize an array index, handling negative indices.
|
|
@@ -20,8 +24,8 @@ pub fn normalize_index(idx: &Value, len: i64, default: usize) -> usize {
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/// Create an error object with a single "error" field.
|
|
23
|
-
pub fn make_error_value(e: impl
|
|
24
|
-
let mut obj = ObjectMap::
|
|
27
|
+
pub fn make_error_value(e: impl core::fmt::Display) -> Value {
|
|
28
|
+
let mut obj = ObjectMap::with_capacity_and_hasher(1, Default::default());
|
|
25
29
|
obj.insert(Arc::from("error"), Value::String(e.to_string().into()));
|
|
26
30
|
Value::object(obj)
|
|
27
31
|
}
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
//! array-returning version behaved; mid-iteration mutation of the source is not reflected (a
|
|
13
13
|
//! live iterator is a follow-up).
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
#[cfg(feature = "portable")]
|
|
16
|
+
#[allow(unused_imports)]
|
|
17
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
18
|
+
|
|
19
|
+
use tishlang_core::Arc;
|
|
16
20
|
|
|
17
21
|
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
18
22
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
//! Used by the compiled runtime (tishlang_runtime) and bytecode VM (tishlang_vm). The
|
|
4
4
|
//! interpreter (tishlang_eval) implements builtins inline due to different Value
|
|
5
5
|
//! and native signatures.
|
|
6
|
+
#![cfg_attr(feature = "portable", no_std)]
|
|
7
|
+
|
|
8
|
+
extern crate alloc;
|
|
6
9
|
|
|
7
10
|
pub mod array;
|
|
8
11
|
pub mod collections;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
//! Math builtin functions.
|
|
2
2
|
|
|
3
|
+
#[cfg(feature = "portable")]
|
|
4
|
+
#[allow(unused_imports)]
|
|
5
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
6
|
+
#[cfg(feature = "portable")]
|
|
7
|
+
use tishlang_core::FloatExt;
|
|
8
|
+
|
|
3
9
|
use crate::helpers::extract_num;
|
|
4
10
|
use tishlang_core::{to_int32, to_uint32, Value};
|
|
5
11
|
|
|
@@ -120,10 +126,18 @@ pub fn pow(args: &[Value]) -> Value {
|
|
|
120
126
|
Value::Number(base.powf(exp))
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
#[cfg(not(feature = "portable"))]
|
|
123
130
|
pub fn random(_args: &[Value]) -> Value {
|
|
124
131
|
Value::Number(rand::random::<f64>())
|
|
125
132
|
}
|
|
126
133
|
|
|
134
|
+
/// No OS entropy on GBA: read the runtime-installed RNG hook (a seedable xorshift
|
|
135
|
+
/// fallback until the facade wires up `agb::rng`). See `tishlang_core::install_rng`.
|
|
136
|
+
#[cfg(feature = "portable")]
|
|
137
|
+
pub fn random(_args: &[Value]) -> Value {
|
|
138
|
+
Value::Number(tishlang_core::random_f64())
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
pub fn sign(args: &[Value]) -> Value {
|
|
128
142
|
let n = extract_num(args.first()).unwrap_or(f64::NAN);
|
|
129
143
|
Value::Number(if n.is_nan() {
|