@tishlang/tish-lint 2.39.0 → 2.43.8
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-lint +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- 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 +38 -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 +806 -46
- 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 +148 -10
- package/crates/tish_compile/src/schemes.rs +278 -0
- package/crates/tish_compile/src/types.rs +141 -2
- 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_gba_numerics_gated.rs +58 -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 +19 -1
- package/crates/tish_core/src/json.rs +34 -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 +182 -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_lint/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 +54 -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 +620 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +535 -173
- package/justfile +9 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lint +0 -0
- package/platform/darwin-x64/tish-lint +0 -0
- package/platform/linux-arm64/tish-lint +0 -0
- package/platform/linux-x64/tish-lint +0 -0
- package/platform/win32-x64/tish-lint.exe +0 -0
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
//! Unified Value type for Tish runtime values.
|
|
2
2
|
|
|
3
|
-
use
|
|
4
|
-
use std::sync::Arc;
|
|
5
|
-
|
|
6
|
-
use ahash::AHashMap;
|
|
3
|
+
use crate::compat::{AHashMap, ArcStr, AtomicU64, Arc, Ordering, RandomState};
|
|
7
4
|
use indexmap::IndexMap;
|
|
8
5
|
use smallvec::SmallVec;
|
|
9
6
|
|
|
7
|
+
#[cfg(feature = "portable")]
|
|
8
|
+
use alloc::{
|
|
9
|
+
boxed::Box,
|
|
10
|
+
format,
|
|
11
|
+
string::{String, ToString},
|
|
12
|
+
vec,
|
|
13
|
+
vec::Vec,
|
|
14
|
+
};
|
|
15
|
+
#[cfg(feature = "portable")]
|
|
16
|
+
use crate::compat::FloatExt;
|
|
17
|
+
|
|
10
18
|
use crate::vmref::VmRef;
|
|
11
19
|
|
|
12
20
|
/// Property map for objects and other `Arc<str>` → `Value` tables (VM globals, scopes).
|
|
@@ -75,12 +83,12 @@ use fancy_regex::Regex;
|
|
|
75
83
|
pub trait Callable: Send + Sync {
|
|
76
84
|
fn call(&self, args: &[Value]) -> Value;
|
|
77
85
|
/// Downcast hook for the VM frame path; native adapters return themselves (downcast fails).
|
|
78
|
-
fn as_any(&self) -> &dyn
|
|
86
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
79
87
|
}
|
|
80
88
|
#[cfg(not(feature = "send-values"))]
|
|
81
89
|
pub trait Callable {
|
|
82
90
|
fn call(&self, args: &[Value]) -> Value;
|
|
83
|
-
fn as_any(&self) -> &dyn
|
|
91
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
/// Adapter wrapping a plain `Fn` closure (every native builtin) as a [`Callable`].
|
|
@@ -91,7 +99,7 @@ impl<F: Fn(&[Value]) -> Value + Send + Sync + 'static> Callable for FnCallable<F
|
|
|
91
99
|
fn call(&self, args: &[Value]) -> Value {
|
|
92
100
|
(self.0)(args)
|
|
93
101
|
}
|
|
94
|
-
fn as_any(&self) -> &dyn
|
|
102
|
+
fn as_any(&self) -> &dyn core::any::Any {
|
|
95
103
|
self
|
|
96
104
|
}
|
|
97
105
|
}
|
|
@@ -101,7 +109,7 @@ impl<F: Fn(&[Value]) -> Value + 'static> Callable for FnCallable<F> {
|
|
|
101
109
|
fn call(&self, args: &[Value]) -> Value {
|
|
102
110
|
(self.0)(args)
|
|
103
111
|
}
|
|
104
|
-
fn as_any(&self) -> &dyn
|
|
112
|
+
fn as_any(&self) -> &dyn core::any::Any {
|
|
105
113
|
self
|
|
106
114
|
}
|
|
107
115
|
}
|
|
@@ -109,7 +117,7 @@ impl<F: Fn(&[Value]) -> Value + 'static> Callable for FnCallable<F> {
|
|
|
109
117
|
#[cfg(feature = "send-values")]
|
|
110
118
|
pub type NativeFn = Arc<dyn Callable>;
|
|
111
119
|
#[cfg(not(feature = "send-values"))]
|
|
112
|
-
pub type NativeFn =
|
|
120
|
+
pub type NativeFn = alloc::rc::Rc<dyn Callable>;
|
|
113
121
|
|
|
114
122
|
/// Build a raw [`NativeFn`] from a plain closure (wraps it in [`FnCallable`]). For sites that
|
|
115
123
|
/// need a `NativeFn` handle directly rather than a `Value::Function` (e.g. HTTP/promise/timer
|
|
@@ -120,7 +128,7 @@ pub fn native_fn<F: Fn(&[Value]) -> Value + Send + Sync + 'static>(f: F) -> Nati
|
|
|
120
128
|
}
|
|
121
129
|
#[cfg(not(feature = "send-values"))]
|
|
122
130
|
pub fn native_fn<F: Fn(&[Value]) -> Value + 'static>(f: F) -> NativeFn {
|
|
123
|
-
|
|
131
|
+
alloc::rc::Rc::new(FnCallable(f))
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
/// Trait for opaque Rust types exposed to Tish (e.g. Polars DataFrame).
|
|
@@ -141,7 +149,7 @@ pub trait TishOpaque: Send + Sync {
|
|
|
141
149
|
fn get_method(&self, name: &str) -> Option<NativeFn>;
|
|
142
150
|
|
|
143
151
|
/// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
|
|
144
|
-
fn as_any(&self) -> &dyn
|
|
152
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
/// Single-threaded variant (no `Send + Sync` bound); see the `send-values` doc above.
|
|
@@ -154,13 +162,13 @@ pub trait TishOpaque {
|
|
|
154
162
|
fn get_method(&self, name: &str) -> Option<NativeFn>;
|
|
155
163
|
|
|
156
164
|
/// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
|
|
157
|
-
fn as_any(&self) -> &dyn
|
|
165
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
/// Trait for Promise-like values that can be awaited (block until settled).
|
|
161
169
|
/// Implemented by the runtime for native compile; interpreter uses its own Promise.
|
|
162
170
|
pub trait TishPromise: Send + Sync {
|
|
163
|
-
fn block_until_settled(&self) ->
|
|
171
|
+
fn block_until_settled(&self) -> core::result::Result<Value, Value>;
|
|
164
172
|
/// Try to settle WITHOUT blocking. Returns `Some(result)` if the promise was already
|
|
165
173
|
/// settled before this call; returns `None` if it is still pending (a background thread
|
|
166
174
|
/// / I/O task has not completed yet). Default: always pending — implementors of async
|
|
@@ -169,7 +177,7 @@ pub trait TishPromise: Send + Sync {
|
|
|
169
177
|
/// Used by `race`/`any`/`allSettled` to handle already-settled promises in input-order
|
|
170
178
|
/// (deterministic, JS-compatible) before falling back to concurrent thread waiting for
|
|
171
179
|
/// genuinely-pending ones.
|
|
172
|
-
fn try_settle(&self) -> Option<
|
|
180
|
+
fn try_settle(&self) -> Option<core::result::Result<Value, Value>> {
|
|
173
181
|
None
|
|
174
182
|
}
|
|
175
183
|
}
|
|
@@ -236,8 +244,8 @@ impl RegExpFlags {
|
|
|
236
244
|
}
|
|
237
245
|
|
|
238
246
|
#[cfg(feature = "regex")]
|
|
239
|
-
impl
|
|
240
|
-
fn fmt(&self, f: &mut
|
|
247
|
+
impl core::fmt::Display for RegExpFlags {
|
|
248
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
241
249
|
if self.global {
|
|
242
250
|
f.write_str("g")?;
|
|
243
251
|
}
|
|
@@ -344,20 +352,111 @@ impl TishRegExp {
|
|
|
344
352
|
/// **Thread safety**: `Value: Send + Sync`. Mutable payloads live inside
|
|
345
353
|
/// [`VmRef`], a `Send + Sync` `Arc<Mutex<T>>` wrapper that preserves the
|
|
346
354
|
/// `RefCell`-style borrow API. Functions are `Arc<dyn Fn + Send + Sync>`.
|
|
355
|
+
/// Backing store for a [`Value::NumberArray`] (#199 packed arrays). Starts `Packed`; a mutation
|
|
356
|
+
/// that would introduce a non-numeric element upgrades it to `Boxed` **in place** via
|
|
357
|
+
/// [`NumArrayBacking::deopt`], so all aliases sharing the `VmRef` observe the change (#506).
|
|
358
|
+
///
|
|
359
|
+
/// Access rules that keep the two arms sound at every call site:
|
|
360
|
+
/// - single-element read → [`get`](Self::get) (O(1), no alloc);
|
|
361
|
+
/// - iterate/snapshot as `Value`s → [`to_values`](Self::to_values) (Packed boxes each f64, same
|
|
362
|
+
/// cost the old `materialize` path paid);
|
|
363
|
+
/// - hot packed-only fast path (sort/fold/`sum`) → [`as_packed`](Self::as_packed) /
|
|
364
|
+
/// [`as_packed_mut`](Self::as_packed_mut), which return `None` after a deopt so the caller falls
|
|
365
|
+
/// back to the boxed path instead of assuming `Vec<f64>`.
|
|
366
|
+
#[derive(Clone, Debug)]
|
|
367
|
+
pub enum NumArrayBacking {
|
|
368
|
+
Packed(Vec<f64>),
|
|
369
|
+
Boxed(Vec<Value>),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
impl NumArrayBacking {
|
|
373
|
+
#[inline]
|
|
374
|
+
pub fn len(&self) -> usize {
|
|
375
|
+
match self {
|
|
376
|
+
NumArrayBacking::Packed(v) => v.len(),
|
|
377
|
+
NumArrayBacking::Boxed(v) => v.len(),
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
#[inline]
|
|
381
|
+
pub fn is_empty(&self) -> bool {
|
|
382
|
+
self.len() == 0
|
|
383
|
+
}
|
|
384
|
+
/// Element `i` as a `Value` (Packed: `Number(n)`, NaN kept as `Number(NaN)` — the hole-as-null
|
|
385
|
+
/// rule is applied by the specific read sites that want it, preserving current per-site
|
|
386
|
+
/// behavior). `None` if out of bounds.
|
|
387
|
+
#[inline]
|
|
388
|
+
pub fn get(&self, i: usize) -> Option<Value> {
|
|
389
|
+
match self {
|
|
390
|
+
NumArrayBacking::Packed(v) => v.get(i).map(|n| Value::Number(*n)),
|
|
391
|
+
NumArrayBacking::Boxed(v) => v.get(i).cloned(),
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/// Snapshot the whole array as boxed `Value`s (Packed boxes each `f64`).
|
|
395
|
+
#[inline]
|
|
396
|
+
pub fn to_values(&self) -> Vec<Value> {
|
|
397
|
+
match self {
|
|
398
|
+
NumArrayBacking::Packed(v) => v.iter().map(|n| Value::Number(*n)).collect(),
|
|
399
|
+
NumArrayBacking::Boxed(v) => v.clone(),
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/// The packed `f64` slice, iff still packed (hot fast paths); `None` once deopted.
|
|
403
|
+
#[inline]
|
|
404
|
+
pub fn as_packed(&self) -> Option<&Vec<f64>> {
|
|
405
|
+
match self {
|
|
406
|
+
NumArrayBacking::Packed(v) => Some(v),
|
|
407
|
+
NumArrayBacking::Boxed(_) => None,
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
#[inline]
|
|
411
|
+
pub fn as_packed_mut(&mut self) -> Option<&mut Vec<f64>> {
|
|
412
|
+
match self {
|
|
413
|
+
NumArrayBacking::Packed(v) => Some(v),
|
|
414
|
+
NumArrayBacking::Boxed(_) => None,
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/// The boxed slice, iff already deopted.
|
|
418
|
+
#[inline]
|
|
419
|
+
pub fn as_boxed(&self) -> Option<&Vec<Value>> {
|
|
420
|
+
match self {
|
|
421
|
+
NumArrayBacking::Boxed(v) => Some(v),
|
|
422
|
+
NumArrayBacking::Packed(_) => None,
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/// Upgrade to a boxed backing **in place** (idempotent) and return `&mut` to the boxed `Vec`.
|
|
426
|
+
/// Every alias sharing this cell now sees a boxed array (#506). Packed `f64`s become
|
|
427
|
+
/// `Number(n)` (NaN preserved), so a read after deopt matches what the packed form returned.
|
|
428
|
+
#[inline]
|
|
429
|
+
pub fn deopt(&mut self) -> &mut Vec<Value> {
|
|
430
|
+
if let NumArrayBacking::Packed(v) = self {
|
|
431
|
+
let boxed = v.iter().map(|n| Value::Number(*n)).collect();
|
|
432
|
+
*self = NumArrayBacking::Boxed(boxed);
|
|
433
|
+
}
|
|
434
|
+
match self {
|
|
435
|
+
NumArrayBacking::Boxed(v) => v,
|
|
436
|
+
NumArrayBacking::Packed(_) => unreachable!("just deopted"),
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
347
441
|
#[derive(Clone, Default)]
|
|
348
442
|
pub enum Value {
|
|
349
443
|
Number(f64),
|
|
350
|
-
String(
|
|
444
|
+
String(ArcStr),
|
|
351
445
|
Bool(bool),
|
|
352
446
|
#[default]
|
|
353
447
|
Null,
|
|
354
448
|
Array(VmRef<Vec<Value>>),
|
|
355
|
-
///
|
|
356
|
-
///
|
|
357
|
-
///
|
|
358
|
-
///
|
|
359
|
-
///
|
|
360
|
-
|
|
449
|
+
/// Numeric-origin array — `TISH_PACKED_ARRAYS` mode only. Starts `Packed` (a `Vec<f64>`, no
|
|
450
|
+
/// per-element boxing; enables direct `sort_unstable_by` and unboxed folds). A mutation that
|
|
451
|
+
/// introduces a **non-numeric** value ([`NumArrayBacking::deopt`]) upgrades the backing to
|
|
452
|
+
/// `Boxed` IN PLACE, so every alias sharing this `VmRef` observes the transition (#506) — the
|
|
453
|
+
/// value stays a `NumberArray` (aliases can't be rewritten to `Array`), but its backing is now
|
|
454
|
+
/// a `Vec<Value>` and it behaves exactly like a boxed array. So the tag means "numeric-origin,
|
|
455
|
+
/// possibly deopted"; access goes through the [`NumArrayBacking`] accessors, never a raw
|
|
456
|
+
/// `Vec<f64>` assumption. Created by all-numeric array literals, `new Array(n)` (zero-filled),
|
|
457
|
+
/// numeric HOF results, and typed arrays. Never created when `packed_arrays_enabled()` is
|
|
458
|
+
/// false — callers check before constructing.
|
|
459
|
+
NumberArray(VmRef<NumArrayBacking>),
|
|
361
460
|
Object(VmRef<ObjectData>),
|
|
362
461
|
/// ECMAScript-style primitive symbol (identity by `Arc`).
|
|
363
462
|
Symbol(Arc<TishSymbol>),
|
|
@@ -381,7 +480,7 @@ pub enum Value {
|
|
|
381
480
|
// re-attempt the box trick; only a dispatch-level change (e.g. NaN-box's branch-free tag test, not
|
|
382
481
|
// its size) could pay off. Gated to 64-bit: wasm32 (wasi) has 32-bit pointers, so size differs there.
|
|
383
482
|
#[cfg(target_pointer_width = "64")]
|
|
384
|
-
const _: () = assert!(
|
|
483
|
+
const _: () = assert!(core::mem::size_of::<Value>() == 24);
|
|
385
484
|
|
|
386
485
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
387
486
|
// #201 Stage A — representation abstraction (behavior-preserving, zero perf effect).
|
|
@@ -422,11 +521,11 @@ pub enum ValueTag {
|
|
|
422
521
|
/// sites. Zero-cost — each arm borrows the existing payload in place.
|
|
423
522
|
pub enum ValueRef<'a> {
|
|
424
523
|
Number(f64),
|
|
425
|
-
String(&'a
|
|
524
|
+
String(&'a ArcStr),
|
|
426
525
|
Bool(bool),
|
|
427
526
|
Null,
|
|
428
527
|
Array(&'a VmRef<Vec<Value>>),
|
|
429
|
-
NumberArray(&'a VmRef<
|
|
528
|
+
NumberArray(&'a VmRef<NumArrayBacking>),
|
|
430
529
|
Object(&'a VmRef<ObjectData>),
|
|
431
530
|
Symbol(&'a Arc<TishSymbol>),
|
|
432
531
|
Function(&'a NativeFn),
|
|
@@ -452,7 +551,7 @@ impl Value {
|
|
|
452
551
|
/// Named constructor for a string (#201 abstraction). Accepts anything that converts
|
|
453
552
|
/// into the interned `ArcStr` (`&str`, `String`, `ArcStr`).
|
|
454
553
|
#[inline]
|
|
455
|
-
pub fn string(s: impl Into<
|
|
554
|
+
pub fn string(s: impl Into<ArcStr>) -> Self {
|
|
456
555
|
Value::String(s.into())
|
|
457
556
|
}
|
|
458
557
|
|
|
@@ -540,7 +639,7 @@ impl Value {
|
|
|
540
639
|
|
|
541
640
|
/// Borrow the packed-number-array payload, if this is a `NumberArray`.
|
|
542
641
|
#[inline]
|
|
543
|
-
pub fn as_number_array(&self) -> Option<&VmRef<
|
|
642
|
+
pub fn as_number_array(&self) -> Option<&VmRef<NumArrayBacking>> {
|
|
544
643
|
match self {
|
|
545
644
|
Value::NumberArray(a) => Some(a),
|
|
546
645
|
_ => None,
|
|
@@ -571,7 +670,7 @@ const PROPMAP_INLINE: usize = 8;
|
|
|
571
670
|
#[derive(Clone, Debug, Default)]
|
|
572
671
|
pub struct PropMap {
|
|
573
672
|
inline: SmallVec<[(Arc<str>, Value); PROPMAP_INLINE]>,
|
|
574
|
-
map: Option<Box<IndexMap<Arc<str>, Value,
|
|
673
|
+
map: Option<Box<IndexMap<Arc<str>, Value, RandomState>>>,
|
|
575
674
|
/// Hidden-class identity for this object's ordered key-set (JSC Structure). `EMPTY_SHAPE` (0)
|
|
576
675
|
/// for `{}`. Maintained by `insert` (new key → `shape::transition`) and reset to `DICT_SHAPE` by
|
|
577
676
|
/// `remove`. Lets the VM's inline caches compare a `u32` instead of hashing a key. INVARIANT: a
|
|
@@ -592,7 +691,7 @@ impl PropMap {
|
|
|
592
691
|
inline: SmallVec::new(),
|
|
593
692
|
map: Some(Box::new(IndexMap::with_capacity_and_hasher(
|
|
594
693
|
n,
|
|
595
|
-
|
|
694
|
+
RandomState::default(),
|
|
596
695
|
))),
|
|
597
696
|
shape: crate::shape::EMPTY_SHAPE,
|
|
598
697
|
}
|
|
@@ -693,15 +792,15 @@ impl PropMap {
|
|
|
693
792
|
}
|
|
694
793
|
if let Some(slot) = self.inline.iter_mut().find(|(k, _)| k.as_ref() == key.as_ref()) {
|
|
695
794
|
// Update existing key → value changes, layout (shape) does not.
|
|
696
|
-
return Some(
|
|
795
|
+
return Some(core::mem::replace(&mut slot.1, val));
|
|
697
796
|
}
|
|
698
797
|
// New key (inline storage) → transition the shape away from the current one.
|
|
699
798
|
self.shape = crate::shape::transition(self.shape, &key);
|
|
700
799
|
if self.inline.len() >= PROPMAP_INLINE {
|
|
701
800
|
// Promote inline storage to an insertion-ordered map (keys + their order are preserved,
|
|
702
801
|
// so the shape stays valid).
|
|
703
|
-
let mut m: IndexMap<Arc<str>, Value,
|
|
704
|
-
IndexMap::with_capacity_and_hasher(self.inline.len() + 1,
|
|
802
|
+
let mut m: IndexMap<Arc<str>, Value, RandomState> =
|
|
803
|
+
IndexMap::with_capacity_and_hasher(self.inline.len() + 1, RandomState::default());
|
|
705
804
|
for (k, v) in self.inline.drain(..) {
|
|
706
805
|
m.insert(k, v);
|
|
707
806
|
}
|
|
@@ -778,9 +877,9 @@ impl PropMap {
|
|
|
778
877
|
// If the combined worst-case size escapes inline storage, promote once up front so the
|
|
779
878
|
// per-key inserts below never reallocate or re-promote.
|
|
780
879
|
if self.map.is_none() && self.inline.len() + incoming > PROPMAP_INLINE {
|
|
781
|
-
let mut m: IndexMap<Arc<str>, Value,
|
|
880
|
+
let mut m: IndexMap<Arc<str>, Value, RandomState> = IndexMap::with_capacity_and_hasher(
|
|
782
881
|
self.inline.len() + incoming,
|
|
783
|
-
|
|
882
|
+
RandomState::default(),
|
|
784
883
|
);
|
|
785
884
|
for (k, v) in self.inline.drain(..) {
|
|
786
885
|
m.insert(k, v);
|
|
@@ -826,7 +925,7 @@ impl IntoIterator for PropMap {
|
|
|
826
925
|
|
|
827
926
|
/// Zero-allocation borrowing iterator over [`PropMap`] entries (insertion order).
|
|
828
927
|
pub enum PropMapIter<'a> {
|
|
829
|
-
Inline(
|
|
928
|
+
Inline(core::slice::Iter<'a, (Arc<str>, Value)>),
|
|
830
929
|
Map(indexmap::map::Iter<'a, Arc<str>, Value>),
|
|
831
930
|
}
|
|
832
931
|
impl<'a> Iterator for PropMapIter<'a> {
|
|
@@ -842,7 +941,7 @@ impl<'a> Iterator for PropMapIter<'a> {
|
|
|
842
941
|
|
|
843
942
|
/// Zero-allocation key iterator over [`PropMap`] (insertion order).
|
|
844
943
|
pub enum PropMapKeys<'a> {
|
|
845
|
-
Inline(
|
|
944
|
+
Inline(core::slice::Iter<'a, (Arc<str>, Value)>),
|
|
846
945
|
Map(indexmap::map::Keys<'a, Arc<str>, Value>),
|
|
847
946
|
}
|
|
848
947
|
impl<'a> Iterator for PropMapKeys<'a> {
|
|
@@ -858,7 +957,7 @@ impl<'a> Iterator for PropMapKeys<'a> {
|
|
|
858
957
|
|
|
859
958
|
/// Zero-allocation value iterator over [`PropMap`] (insertion order).
|
|
860
959
|
pub enum PropMapValues<'a> {
|
|
861
|
-
Inline(
|
|
960
|
+
Inline(core::slice::Iter<'a, (Arc<str>, Value)>),
|
|
862
961
|
Map(indexmap::map::Values<'a, Arc<str>, Value>),
|
|
863
962
|
}
|
|
864
963
|
impl<'a> Iterator for PropMapValues<'a> {
|
|
@@ -894,6 +993,10 @@ impl Iterator for PropMapIntoIter {
|
|
|
894
993
|
pub struct ObjectData {
|
|
895
994
|
pub strings: PropMap,
|
|
896
995
|
pub symbols: Option<AHashMap<u64, Value>>,
|
|
996
|
+
/// `Object.freeze` marker (#437). When true, property writes must throw a catchable TypeError
|
|
997
|
+
/// instead of mutating — enforced at every write chokepoint (interp assign, vm `set_member`,
|
|
998
|
+
/// native `set_prop`). Reference-shared like the rest of the object; a fresh object is not frozen.
|
|
999
|
+
pub frozen: bool,
|
|
897
1000
|
}
|
|
898
1001
|
|
|
899
1002
|
impl ObjectData {
|
|
@@ -902,9 +1005,16 @@ impl ObjectData {
|
|
|
902
1005
|
Self {
|
|
903
1006
|
strings: strings.into_iter().collect(),
|
|
904
1007
|
symbols: None,
|
|
1008
|
+
frozen: false,
|
|
905
1009
|
}
|
|
906
1010
|
}
|
|
907
1011
|
|
|
1012
|
+
/// True if `Object.freeze` has been applied to this object.
|
|
1013
|
+
#[inline]
|
|
1014
|
+
pub fn is_frozen(&self) -> bool {
|
|
1015
|
+
self.frozen
|
|
1016
|
+
}
|
|
1017
|
+
|
|
908
1018
|
#[inline]
|
|
909
1019
|
pub fn len_entries(&self) -> usize {
|
|
910
1020
|
self.strings.len() + self.symbols.as_ref().map(|s| s.len()).unwrap_or(0)
|
|
@@ -1084,15 +1194,15 @@ pub fn value_call(callee: &Value, args: &[Value]) -> Value {
|
|
|
1084
1194
|
return value_call(&inner, args);
|
|
1085
1195
|
}
|
|
1086
1196
|
crate::set_pending_throw(crate::not_a_function_error(format!(
|
|
1087
|
-
"
|
|
1088
|
-
callee.
|
|
1197
|
+
"Call of non-function: {}",
|
|
1198
|
+
callee.type_name()
|
|
1089
1199
|
)));
|
|
1090
1200
|
Value::Null
|
|
1091
1201
|
}
|
|
1092
1202
|
_ => {
|
|
1093
1203
|
crate::set_pending_throw(crate::not_a_function_error(format!(
|
|
1094
|
-
"
|
|
1095
|
-
callee.
|
|
1204
|
+
"Call of non-function: {}",
|
|
1205
|
+
callee.type_name()
|
|
1096
1206
|
)));
|
|
1097
1207
|
Value::Null
|
|
1098
1208
|
}
|
|
@@ -1118,11 +1228,15 @@ pub fn merge_object_data(left: &VmRef<ObjectData>, right: &VmRef<ObjectData>) ->
|
|
|
1118
1228
|
None => symbols = Some(rs.clone()),
|
|
1119
1229
|
}
|
|
1120
1230
|
}
|
|
1121
|
-
ObjectData {
|
|
1231
|
+
ObjectData {
|
|
1232
|
+
strings,
|
|
1233
|
+
symbols,
|
|
1234
|
+
frozen: false,
|
|
1235
|
+
}
|
|
1122
1236
|
}
|
|
1123
1237
|
|
|
1124
|
-
impl
|
|
1125
|
-
fn fmt(&self, f: &mut
|
|
1238
|
+
impl core::fmt::Debug for Value {
|
|
1239
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
1126
1240
|
match self {
|
|
1127
1241
|
Value::Number(n) => write!(f, "Number({})", n),
|
|
1128
1242
|
Value::String(s) => write!(f, "String({:?})", s.as_str()),
|
|
@@ -1242,7 +1356,7 @@ pub fn js_number_to_string_into(out: &mut String, value: f64) {
|
|
|
1242
1356
|
end -= 1;
|
|
1243
1357
|
}
|
|
1244
1358
|
let trail = (n - end) as i32;
|
|
1245
|
-
let digits =
|
|
1359
|
+
let digits = core::str::from_utf8(&digits_buf[lead..end]).expect("ascii digits");
|
|
1246
1360
|
let k = digits.len() as i32; // significant digit count (≤ 17 for an f64)
|
|
1247
1361
|
let point = k + exp - frac_len + trail;
|
|
1248
1362
|
|
|
@@ -1318,8 +1432,12 @@ impl Value {
|
|
|
1318
1432
|
Value::NumberArray(arr) => {
|
|
1319
1433
|
let inner: Vec<String> = arr
|
|
1320
1434
|
.borrow()
|
|
1435
|
+
.to_values()
|
|
1321
1436
|
.iter()
|
|
1322
|
-
.map(
|
|
1437
|
+
.map(|v| match v {
|
|
1438
|
+
Value::Number(n) if n.is_nan() => "null".to_string(),
|
|
1439
|
+
other => other.to_display_string(),
|
|
1440
|
+
})
|
|
1323
1441
|
.collect();
|
|
1324
1442
|
format!("[{}]", inner.join(", "))
|
|
1325
1443
|
}
|
|
@@ -1393,8 +1511,9 @@ impl Value {
|
|
|
1393
1511
|
}
|
|
1394
1512
|
Value::NumberArray(arr) => arr
|
|
1395
1513
|
.borrow()
|
|
1514
|
+
.to_values()
|
|
1396
1515
|
.iter()
|
|
1397
|
-
.map(|
|
|
1516
|
+
.map(|v| v.to_js_string())
|
|
1398
1517
|
.collect::<Vec<_>>()
|
|
1399
1518
|
.join(","),
|
|
1400
1519
|
Value::Object(_) => "[object Object]".to_string(),
|
|
@@ -1436,7 +1555,7 @@ impl Value {
|
|
|
1436
1555
|
#[cfg(feature = "send-values")]
|
|
1437
1556
|
(Value::Function(a), Value::Function(b)) => Arc::ptr_eq(a, b),
|
|
1438
1557
|
#[cfg(not(feature = "send-values"))]
|
|
1439
|
-
(Value::Function(a), Value::Function(b)) =>
|
|
1558
|
+
(Value::Function(a), Value::Function(b)) => alloc::rc::Rc::ptr_eq(a, b),
|
|
1440
1559
|
#[cfg(feature = "regex")]
|
|
1441
1560
|
(Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
|
|
1442
1561
|
(Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
|
|
@@ -1465,7 +1584,7 @@ impl Value {
|
|
|
1465
1584
|
where
|
|
1466
1585
|
F: Fn(&[Value]) -> Value + 'static,
|
|
1467
1586
|
{
|
|
1468
|
-
Value::Function(
|
|
1587
|
+
Value::Function(alloc::rc::Rc::new(FnCallable(f)))
|
|
1469
1588
|
}
|
|
1470
1589
|
|
|
1471
1590
|
/// Create a new array Value from a Vec.
|
|
@@ -1490,6 +1609,7 @@ impl Value {
|
|
|
1490
1609
|
Value::Object(VmRef::new(ObjectData {
|
|
1491
1610
|
strings,
|
|
1492
1611
|
symbols: None,
|
|
1612
|
+
frozen: false,
|
|
1493
1613
|
}))
|
|
1494
1614
|
}
|
|
1495
1615
|
|
|
@@ -1509,6 +1629,7 @@ impl Value {
|
|
|
1509
1629
|
/// process starts (as the CI sweep does); mid-process toggling is not observed by design.
|
|
1510
1630
|
/// The flag is intentionally backwards from the slot/JIT flags (those were default-on) to
|
|
1511
1631
|
/// keep the default binary behaviour byte-identical while we validate coverage.
|
|
1632
|
+
#[cfg(not(feature = "portable"))]
|
|
1512
1633
|
#[inline]
|
|
1513
1634
|
pub fn packed_arrays_enabled() -> bool {
|
|
1514
1635
|
static PACKED_ARRAYS: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
|
@@ -1517,10 +1638,18 @@ impl Value {
|
|
|
1517
1638
|
})
|
|
1518
1639
|
}
|
|
1519
1640
|
|
|
1641
|
+
/// Packed f64 arrays are off under `portable` (no env to opt in; keeps the
|
|
1642
|
+
/// generic array path, which the GBA runtime already exercises).
|
|
1643
|
+
#[cfg(feature = "portable")]
|
|
1644
|
+
#[inline]
|
|
1645
|
+
pub fn packed_arrays_enabled() -> bool {
|
|
1646
|
+
false
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1520
1649
|
/// Wrap a `Vec<f64>` as a `Value::NumberArray`. Only call when `packed_arrays_enabled()`.
|
|
1521
1650
|
#[inline]
|
|
1522
1651
|
pub fn number_array(items: Vec<f64>) -> Self {
|
|
1523
|
-
Value::NumberArray(VmRef::new(items))
|
|
1652
|
+
Value::NumberArray(VmRef::new(NumArrayBacking::Packed(items)))
|
|
1524
1653
|
}
|
|
1525
1654
|
|
|
1526
1655
|
/// Materialize a `Value::NumberArray` into a boxed `Value::Array`.
|
|
@@ -1529,9 +1658,8 @@ impl Value {
|
|
|
1529
1658
|
/// converts once and continues on the generic path. The original `NumberArray` VmRef
|
|
1530
1659
|
/// is consumed; callers replace the `Value` in whatever container held it.
|
|
1531
1660
|
#[inline]
|
|
1532
|
-
pub fn materialize_number_array(arr: &VmRef<
|
|
1533
|
-
|
|
1534
|
-
Value::Array(VmRef::new(nums.iter().map(|&n| Value::Number(n)).collect()))
|
|
1661
|
+
pub fn materialize_number_array(arr: &VmRef<NumArrayBacking>) -> Value {
|
|
1662
|
+
Value::Array(VmRef::new(arr.borrow().to_values()))
|
|
1535
1663
|
}
|
|
1536
1664
|
|
|
1537
1665
|
/// If `self` is a `NumberArray`, materialise and return `Value::Array`; otherwise
|
|
@@ -49,24 +49,24 @@
|
|
|
49
49
|
//! feature. They both `Deref` (and, for write guards, `DerefMut`) to `T`
|
|
50
50
|
//! just like the underlying types.
|
|
51
51
|
|
|
52
|
-
use
|
|
52
|
+
use core::fmt;
|
|
53
53
|
|
|
54
54
|
// --------------------------------------------------------------------------
|
|
55
55
|
// Single-threaded backing store (default): Rc<RefCell<T>>
|
|
56
56
|
// --------------------------------------------------------------------------
|
|
57
57
|
#[cfg(not(feature = "send-values"))]
|
|
58
58
|
mod imp {
|
|
59
|
-
use
|
|
60
|
-
use
|
|
59
|
+
use alloc::rc::Rc;
|
|
60
|
+
use core::cell::RefCell;
|
|
61
61
|
|
|
62
62
|
#[derive(Default)]
|
|
63
63
|
pub struct VmRef<T: ?Sized>(pub(super) Rc<RefCell<T>>);
|
|
64
64
|
|
|
65
65
|
/// Read guard alias. On the single-threaded path this is a true
|
|
66
66
|
/// `Ref<'_, T>`, so multiple readers can coexist.
|
|
67
|
-
pub type ReadGuard<'a, T> =
|
|
67
|
+
pub type ReadGuard<'a, T> = core::cell::Ref<'a, T>;
|
|
68
68
|
/// Write guard alias. Exclusive, `DerefMut`.
|
|
69
|
-
pub type WriteGuard<'a, T> =
|
|
69
|
+
pub type WriteGuard<'a, T> = core::cell::RefMut<'a, T>;
|
|
70
70
|
|
|
71
71
|
impl<T> VmRef<T> {
|
|
72
72
|
#[inline]
|
|
@@ -182,6 +182,7 @@ mod imp {
|
|
|
182
182
|
|
|
183
183
|
pub use imp::{ReadGuard as VmReadGuard, VmRef, WriteGuard as VmWriteGuard};
|
|
184
184
|
|
|
185
|
+
#[cfg(not(feature = "portable"))]
|
|
185
186
|
impl<T: fmt::Debug> fmt::Debug for VmRef<T> {
|
|
186
187
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
187
188
|
// Match `RefCell`'s debug format so snapshot-test output stays
|
|
@@ -195,3 +196,14 @@ impl<T: fmt::Debug> fmt::Debug for VmRef<T> {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
}
|
|
199
|
+
|
|
200
|
+
// No unwinding on GBA (`panic = abort`), so `catch_unwind` is unavailable.
|
|
201
|
+
// Debug is used for host snapshot tests, not the runtime hot path, so a direct
|
|
202
|
+
// borrow is fine (a live borrow during Debug would abort, which never happens in
|
|
203
|
+
// practice on the single-threaded target).
|
|
204
|
+
#[cfg(feature = "portable")]
|
|
205
|
+
impl<T: fmt::Debug> fmt::Debug for VmRef<T> {
|
|
206
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
207
|
+
write!(f, "RefCell {{ value: {:?} }}", &*self.borrow())
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -42,6 +42,8 @@ tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
|
|
|
42
42
|
tishlang_builtins = { path = "../tish_builtins", version = ">=0.1" }
|
|
43
43
|
tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
|
|
44
44
|
tishlang_core = { path = "../tish_core", version = ">=0.1" }
|
|
45
|
+
# Shared platform/surface file cascade (same as `tish build` / `resolve-id`).
|
|
46
|
+
tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
|
|
45
47
|
tiny_http = { version = "0.12.0", optional = true }
|
|
46
48
|
tokio = { version = "1.50.0", features = ["rt-multi-thread", "time", "sync"], optional = true }
|
|
47
49
|
tishlang_runtime = { path = "../tish_runtime", version = ">=0.1", optional = true }
|