@tishlang/tish-format 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.
Files changed (74) hide show
  1. package/Cargo.toml +4 -1
  2. package/bin/tish-format +0 -0
  3. package/crates/tish/src/cli_help.rs +28 -0
  4. package/crates/tish/src/main.rs +41 -5
  5. package/crates/tish/tests/scope_merge.rs +79 -0
  6. package/crates/tish_build_utils/src/lib.rs +27 -1
  7. package/crates/tish_builtins/Cargo.toml +14 -6
  8. package/crates/tish_builtins/src/array.rs +130 -54
  9. package/crates/tish_builtins/src/collections.rs +9 -5
  10. package/crates/tish_builtins/src/construct.rs +7 -1
  11. package/crates/tish_builtins/src/date.rs +15 -1
  12. package/crates/tish_builtins/src/globals.rs +38 -3
  13. package/crates/tish_builtins/src/helpers.rs +7 -3
  14. package/crates/tish_builtins/src/iterator.rs +5 -1
  15. package/crates/tish_builtins/src/lib.rs +3 -0
  16. package/crates/tish_builtins/src/math.rs +14 -0
  17. package/crates/tish_builtins/src/number.rs +72 -6
  18. package/crates/tish_builtins/src/object.rs +6 -1
  19. package/crates/tish_builtins/src/string.rs +13 -2
  20. package/crates/tish_builtins/src/symbol.rs +17 -17
  21. package/crates/tish_builtins/src/typedarrays.rs +9 -2
  22. package/crates/tish_bytecode/src/compiler.rs +76 -2
  23. package/crates/tish_bytecode/src/lib.rs +1 -1
  24. package/crates/tish_bytecode/src/opcode.rs +77 -2
  25. package/crates/tish_compile/src/codegen.rs +806 -46
  26. package/crates/tish_compile/src/infer.rs +37 -41
  27. package/crates/tish_compile/src/lib.rs +13 -1
  28. package/crates/tish_compile/src/platform_resolve.rs +361 -0
  29. package/crates/tish_compile/src/resolve.rs +148 -10
  30. package/crates/tish_compile/src/schemes.rs +278 -0
  31. package/crates/tish_compile/src/types.rs +141 -2
  32. package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
  33. package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
  34. package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
  35. package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
  36. package/crates/tish_compiler_wasm/Cargo.toml +1 -0
  37. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
  38. package/crates/tish_core/Cargo.toml +39 -4
  39. package/crates/tish_core/src/compat.rs +506 -0
  40. package/crates/tish_core/src/console_style.rs +19 -1
  41. package/crates/tish_core/src/json.rs +34 -10
  42. package/crates/tish_core/src/lib.rs +121 -25
  43. package/crates/tish_core/src/macros.rs +1 -1
  44. package/crates/tish_core/src/shape.rs +4 -2
  45. package/crates/tish_core/src/uri.rs +8 -1
  46. package/crates/tish_core/src/value.rs +182 -54
  47. package/crates/tish_core/src/vmref.rs +17 -5
  48. package/crates/tish_eval/Cargo.toml +2 -0
  49. package/crates/tish_eval/src/eval.rs +90 -3
  50. package/crates/tish_eval/src/value.rs +8 -0
  51. package/crates/tish_eval/src/value_convert.rs +316 -21
  52. package/crates/tish_fmt/Cargo.toml +1 -1
  53. package/crates/tish_lsp/Cargo.toml +2 -0
  54. package/crates/tish_lsp/src/import_goto.rs +41 -14
  55. package/crates/tish_lsp/src/main.rs +18 -0
  56. package/crates/tish_native/src/build.rs +246 -0
  57. package/crates/tish_native/src/config.rs +20 -0
  58. package/crates/tish_native/src/lib.rs +16 -6
  59. package/crates/tish_parser/src/lib.rs +28 -0
  60. package/crates/tish_parser/src/parser.rs +25 -4
  61. package/crates/tish_runtime/src/lib.rs +54 -9
  62. package/crates/tish_runtime_gba/Cargo.lock +837 -0
  63. package/crates/tish_runtime_gba/Cargo.toml +16 -0
  64. package/crates/tish_runtime_gba/src/gba.rs +211 -0
  65. package/crates/tish_runtime_gba/src/lib.rs +620 -0
  66. package/crates/tish_vm/src/jit.rs +1446 -164
  67. package/crates/tish_vm/src/vm.rs +535 -173
  68. package/justfile +9 -0
  69. package/package.json +1 -1
  70. package/platform/darwin-arm64/tish-fmt +0 -0
  71. package/platform/darwin-x64/tish-fmt +0 -0
  72. package/platform/linux-arm64/tish-fmt +0 -0
  73. package/platform/linux-x64/tish-fmt +0 -0
  74. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,506 @@
1
+ //! Portability layer for the `portable` (no_std + alloc) build.
2
+ //!
3
+ //! Several items here (the lock/once shims, the clock/RNG hooks) are consumed by
4
+ //! `tishlang_builtins` and the GBA facade rather than by `tishlang_core` itself,
5
+ //! so within this crate they read as unused.
6
+ #![allow(dead_code, unused_imports)]
7
+
8
+ //!
9
+ //! Every use-site that would otherwise reach for a std-only or atomics-bearing
10
+ //! primitive imports it from here instead (`crate::compat::{Arc, ArcStr,
11
+ //! AHashMap, RandomState, AtomicU64, ...}`), so the *same source* compiles for
12
+ //! the host and for `thumbv4t-none-eabi` (Game Boy Advance).
13
+ //!
14
+ //! - **default (std)**: transparent re-exports of `std::sync::Arc`, `ahash`,
15
+ //! `arcstr`, `std` atomics — behaviour is byte-for-byte the pre-split build.
16
+ //! - **`portable`**: atomics-free, alloc-based equivalents — `Rc` for `Arc`,
17
+ //! `FxHasher` for `ahash` (foldhash/ahash both need atomics; see P0 findings),
18
+ //! `portable-atomic` for the one `AtomicU64`, an `Rc<str>` `ArcStr`, and a
19
+ //! `FloatExt` trait supplying the f64 transcendentals `core` lacks.
20
+ //!
21
+ //! GBA is single-core with cooperative scheduling and no preemptive interrupts
22
+ //! touching interpreter state, so the process-global singletons that are
23
+ //! `thread_local!` on the host become single-threaded statics here.
24
+
25
+ // ===========================================================================
26
+ // Standard (hosted) backing — transparent re-exports.
27
+ // ===========================================================================
28
+ #[cfg(not(feature = "portable"))]
29
+ pub use std_backing::*;
30
+
31
+ #[cfg(not(feature = "portable"))]
32
+ mod std_backing {
33
+ pub use ahash::{AHashMap, RandomState};
34
+ pub use arcstr::ArcStr;
35
+ pub use std::sync::atomic::{AtomicU64, Ordering};
36
+ pub use std::sync::{Arc, Mutex, OnceLock, RwLock};
37
+ }
38
+
39
+ // ===========================================================================
40
+ // Portable (no_std + alloc) backing.
41
+ // ===========================================================================
42
+ #[cfg(feature = "portable")]
43
+ pub use portable_backing::*;
44
+
45
+ #[cfg(feature = "portable")]
46
+ mod portable_backing {
47
+ use alloc::rc::Rc;
48
+
49
+ /// Single-core target: reference counting needs no atomics.
50
+ pub use alloc::rc::Rc as Arc;
51
+
52
+ /// 64-bit atomic via `portable-atomic` (critical-section backed on GBA;
53
+ /// the impl is supplied at final link by agb). Only used for the symbol-id
54
+ /// counter, which on a single core is really just a `Cell`, but keeping the
55
+ /// atomic type keeps the source identical to the std path.
56
+ pub use portable_atomic::{AtomicU64, Ordering};
57
+
58
+ /// FxHasher — deterministic, atomics-free, and exactly what `agb_hashmap`
59
+ /// uses on GBA. Drop-in for `ahash::RandomState`.
60
+ pub type RandomState = rustc_hash::FxBuildHasher;
61
+
62
+ /// Drop-in for `ahash::AHashMap<K, V>` (same `default()` / `get` / `insert`
63
+ /// / `iter` / `entry` surface), hashed with FxHasher.
64
+ pub type AHashMap<K, V> = hashbrown::HashMap<K, V, RandomState>;
65
+
66
+ // -- ArcStr: an immutable, cheaply-cloneable string. On the host this is
67
+ // `arcstr::ArcStr` (thin, atomic-refcounted); here it wraps `Rc<str>`.
68
+ // `Rc<str>` is a 16-byte fat pointer, which still fits the `Value`
69
+ // 24-byte budget (the `Arc<dyn ...>` variants already set that width).
70
+ #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71
+ pub struct ArcStr(Rc<str>);
72
+
73
+ impl ArcStr {
74
+ #[inline]
75
+ pub fn as_str(&self) -> &str {
76
+ &self.0
77
+ }
78
+ }
79
+
80
+ impl Default for ArcStr {
81
+ #[inline]
82
+ fn default() -> Self {
83
+ ArcStr(Rc::from(""))
84
+ }
85
+ }
86
+
87
+ impl core::ops::Deref for ArcStr {
88
+ type Target = str;
89
+ #[inline]
90
+ fn deref(&self) -> &str {
91
+ &self.0
92
+ }
93
+ }
94
+
95
+ impl core::convert::AsRef<str> for ArcStr {
96
+ #[inline]
97
+ fn as_ref(&self) -> &str {
98
+ &self.0
99
+ }
100
+ }
101
+
102
+ impl core::borrow::Borrow<str> for ArcStr {
103
+ #[inline]
104
+ fn borrow(&self) -> &str {
105
+ &self.0
106
+ }
107
+ }
108
+
109
+ impl From<&str> for ArcStr {
110
+ #[inline]
111
+ fn from(s: &str) -> Self {
112
+ ArcStr(Rc::from(s))
113
+ }
114
+ }
115
+
116
+ impl From<alloc::string::String> for ArcStr {
117
+ #[inline]
118
+ fn from(s: alloc::string::String) -> Self {
119
+ ArcStr(Rc::from(s.as_str()))
120
+ }
121
+ }
122
+
123
+ impl From<&alloc::string::String> for ArcStr {
124
+ #[inline]
125
+ fn from(s: &alloc::string::String) -> Self {
126
+ ArcStr(Rc::from(s.as_str()))
127
+ }
128
+ }
129
+
130
+ impl From<Rc<str>> for ArcStr {
131
+ #[inline]
132
+ fn from(s: Rc<str>) -> Self {
133
+ ArcStr(s)
134
+ }
135
+ }
136
+
137
+ impl core::fmt::Display for ArcStr {
138
+ #[inline]
139
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
140
+ f.write_str(&self.0)
141
+ }
142
+ }
143
+
144
+ impl core::fmt::Debug for ArcStr {
145
+ #[inline]
146
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
147
+ core::fmt::Debug::fmt(self.as_str(), f)
148
+ }
149
+ }
150
+
151
+ impl PartialEq<str> for ArcStr {
152
+ #[inline]
153
+ fn eq(&self, other: &str) -> bool {
154
+ self.as_str() == other
155
+ }
156
+ }
157
+
158
+ impl PartialEq<&str> for ArcStr {
159
+ #[inline]
160
+ fn eq(&self, other: &&str) -> bool {
161
+ self.as_str() == *other
162
+ }
163
+ }
164
+
165
+ // -- Single-core lock/once shims. GBA runs one cooperative thread, so these
166
+ // never actually contend; they exist only to keep the std API surface
167
+ // (`.read()`/`.write()`/`.lock()`/`.get_or_init()`) at call sites.
168
+ use core::cell::{Ref, RefCell, RefMut};
169
+
170
+ /// Never-poisoned, never-contended stand-in for `std::sync::RwLock`.
171
+ pub struct RwLock<T>(RefCell<T>);
172
+ impl<T> RwLock<T> {
173
+ #[inline]
174
+ pub const fn new(v: T) -> Self {
175
+ RwLock(RefCell::new(v))
176
+ }
177
+ #[inline]
178
+ pub fn read(&self) -> Result<Ref<'_, T>, core::convert::Infallible> {
179
+ Ok(self.0.borrow())
180
+ }
181
+ #[inline]
182
+ pub fn write(&self) -> Result<RefMut<'_, T>, core::convert::Infallible> {
183
+ Ok(self.0.borrow_mut())
184
+ }
185
+ }
186
+
187
+ /// Stand-in for `std::sync::Mutex` (`.lock()` yields a mut guard).
188
+ pub struct Mutex<T>(RefCell<T>);
189
+ impl<T> Mutex<T> {
190
+ #[inline]
191
+ pub const fn new(v: T) -> Self {
192
+ Mutex(RefCell::new(v))
193
+ }
194
+ #[inline]
195
+ pub fn lock(&self) -> Result<RefMut<'_, T>, core::convert::Infallible> {
196
+ Ok(self.0.borrow_mut())
197
+ }
198
+ }
199
+
200
+ /// Stand-in for `std::sync::OnceLock` (`new`/`get`/`get_or_init`/`set`). The GBA has no atomics,
201
+ /// so std's atomic `OnceLock` is unavailable; this wraps the SAFE single-threaded
202
+ /// `core::cell::OnceCell` (which does the write-once interior mutability without hand-rolled
203
+ /// `unsafe`) and only adds `unsafe impl Sync` so it can live in a `static` — sound because the
204
+ /// target is single-core and cooperative, so no concurrent access ever occurs (see `SingleCore`).
205
+ pub struct OnceLock<T>(core::cell::OnceCell<T>);
206
+ // SAFETY: single-core, cooperative; no concurrent access.
207
+ unsafe impl<T> Sync for OnceLock<T> {}
208
+ impl<T> OnceLock<T> {
209
+ #[inline]
210
+ pub const fn new() -> Self {
211
+ OnceLock(core::cell::OnceCell::new())
212
+ }
213
+ #[inline]
214
+ pub fn get(&self) -> Option<&T> {
215
+ self.0.get()
216
+ }
217
+ #[inline]
218
+ pub fn set(&self, value: T) -> Result<(), T> {
219
+ self.0.set(value)
220
+ }
221
+ #[inline]
222
+ pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
223
+ self.0.get_or_init(f)
224
+ }
225
+ }
226
+ impl<T> Default for OnceLock<T> {
227
+ #[inline]
228
+ fn default() -> Self {
229
+ Self::new()
230
+ }
231
+ }
232
+ }
233
+
234
+ // ===========================================================================
235
+ // f64 transcendentals — `core` lacks them; libm supplies them under `portable`.
236
+ //
237
+ // A trait (not free fns) so the SAME `n.abs()` / `n.floor()` source compiles
238
+ // both ways: on the host the inherent `f64` method wins; under `portable` (no
239
+ // inherent methods exist) the trait method is used. Import it only under
240
+ // `portable` at each float use-site to avoid an unused-import warning on host.
241
+ // ===========================================================================
242
+ #[cfg(feature = "portable")]
243
+ pub trait FloatExt {
244
+ fn abs(self) -> Self;
245
+ fn floor(self) -> Self;
246
+ fn ceil(self) -> Self;
247
+ fn trunc(self) -> Self;
248
+ fn fract(self) -> Self;
249
+ fn sqrt(self) -> Self;
250
+ fn cbrt(self) -> Self;
251
+ fn sin(self) -> Self;
252
+ fn cos(self) -> Self;
253
+ fn tan(self) -> Self;
254
+ fn asin(self) -> Self;
255
+ fn acos(self) -> Self;
256
+ fn atan(self) -> Self;
257
+ fn atan2(self, other: Self) -> Self;
258
+ fn ln(self) -> Self;
259
+ fn log10(self) -> Self;
260
+ fn log2(self) -> Self;
261
+ fn exp(self) -> Self;
262
+ fn exp_m1(self) -> Self;
263
+ fn ln_1p(self) -> Self;
264
+ fn powf(self, n: Self) -> Self;
265
+ fn powi(self, n: i32) -> Self;
266
+ fn rem_euclid(self, m: Self) -> Self;
267
+ fn hypot(self, other: Self) -> Self;
268
+ fn round_ties_even(self) -> Self;
269
+ fn sinh(self) -> Self;
270
+ fn cosh(self) -> Self;
271
+ fn tanh(self) -> Self;
272
+ fn asinh(self) -> Self;
273
+ fn acosh(self) -> Self;
274
+ fn atanh(self) -> Self;
275
+ }
276
+
277
+ #[cfg(feature = "portable")]
278
+ impl FloatExt for f64 {
279
+ #[inline]
280
+ fn abs(self) -> f64 {
281
+ libm::fabs(self)
282
+ }
283
+ #[inline]
284
+ fn floor(self) -> f64 {
285
+ libm::floor(self)
286
+ }
287
+ #[inline]
288
+ fn ceil(self) -> f64 {
289
+ libm::ceil(self)
290
+ }
291
+ #[inline]
292
+ fn trunc(self) -> f64 {
293
+ libm::trunc(self)
294
+ }
295
+ #[inline]
296
+ fn fract(self) -> f64 {
297
+ self - libm::trunc(self)
298
+ }
299
+ #[inline]
300
+ fn sqrt(self) -> f64 {
301
+ libm::sqrt(self)
302
+ }
303
+ #[inline]
304
+ fn cbrt(self) -> f64 {
305
+ libm::cbrt(self)
306
+ }
307
+ #[inline]
308
+ fn sin(self) -> f64 {
309
+ libm::sin(self)
310
+ }
311
+ #[inline]
312
+ fn cos(self) -> f64 {
313
+ libm::cos(self)
314
+ }
315
+ #[inline]
316
+ fn tan(self) -> f64 {
317
+ libm::tan(self)
318
+ }
319
+ #[inline]
320
+ fn asin(self) -> f64 {
321
+ libm::asin(self)
322
+ }
323
+ #[inline]
324
+ fn acos(self) -> f64 {
325
+ libm::acos(self)
326
+ }
327
+ #[inline]
328
+ fn atan(self) -> f64 {
329
+ libm::atan(self)
330
+ }
331
+ #[inline]
332
+ fn atan2(self, other: f64) -> f64 {
333
+ libm::atan2(self, other)
334
+ }
335
+ #[inline]
336
+ fn ln(self) -> f64 {
337
+ libm::log(self)
338
+ }
339
+ #[inline]
340
+ fn log10(self) -> f64 {
341
+ libm::log10(self)
342
+ }
343
+ #[inline]
344
+ fn log2(self) -> f64 {
345
+ libm::log2(self)
346
+ }
347
+ #[inline]
348
+ fn exp(self) -> f64 {
349
+ libm::exp(self)
350
+ }
351
+ #[inline]
352
+ fn exp_m1(self) -> f64 {
353
+ libm::expm1(self)
354
+ }
355
+ #[inline]
356
+ fn ln_1p(self) -> f64 {
357
+ libm::log1p(self)
358
+ }
359
+ #[inline]
360
+ fn powf(self, n: f64) -> f64 {
361
+ libm::pow(self, n)
362
+ }
363
+ #[inline]
364
+ fn powi(self, n: i32) -> f64 {
365
+ libm::pow(self, n as f64)
366
+ }
367
+ #[inline]
368
+ fn rem_euclid(self, m: f64) -> f64 {
369
+ let r = libm::fmod(self, m);
370
+ if r < 0.0 {
371
+ r + libm::fabs(m)
372
+ } else {
373
+ r
374
+ }
375
+ }
376
+ #[inline]
377
+ fn hypot(self, other: f64) -> f64 {
378
+ libm::hypot(self, other)
379
+ }
380
+ #[inline]
381
+ fn round_ties_even(self) -> f64 {
382
+ // Round to nearest, ties to even — matches std's `f64::round_ties_even`.
383
+ libm::rint(self)
384
+ }
385
+ #[inline]
386
+ fn sinh(self) -> f64 {
387
+ libm::sinh(self)
388
+ }
389
+ #[inline]
390
+ fn cosh(self) -> f64 {
391
+ libm::cosh(self)
392
+ }
393
+ #[inline]
394
+ fn tanh(self) -> f64 {
395
+ libm::tanh(self)
396
+ }
397
+ #[inline]
398
+ fn asinh(self) -> f64 {
399
+ libm::asinh(self)
400
+ }
401
+ #[inline]
402
+ fn acosh(self) -> f64 {
403
+ libm::acosh(self)
404
+ }
405
+ #[inline]
406
+ fn atanh(self) -> f64 {
407
+ libm::atanh(self)
408
+ }
409
+ }
410
+
411
+ // ===========================================================================
412
+ // Single-core "thread local": a plain static behind a `.with()` API matching
413
+ // `std::thread::LocalKey`, so declaration sites cfg-swap but call sites don't.
414
+ // Sound because GBA runs one cooperative thread and no interrupt handler
415
+ // touches interpreter state.
416
+ // ===========================================================================
417
+ #[cfg(feature = "portable")]
418
+ pub struct SingleCore<T>(T);
419
+
420
+ #[cfg(feature = "portable")]
421
+ // SAFETY: single-core, cooperative, no interrupt handler reenters interpreter
422
+ // state. Access is never concurrent.
423
+ unsafe impl<T> Sync for SingleCore<T> {}
424
+
425
+ #[cfg(feature = "portable")]
426
+ impl<T> SingleCore<T> {
427
+ pub const fn new(value: T) -> Self {
428
+ SingleCore(value)
429
+ }
430
+
431
+ /// Mirrors `LocalKey::with`: hand out a shared ref to the inner value. Interior mutability lives
432
+ /// in `T` (a `RefCell`), so a plain `&self.0` suffices — no `unsafe` block needed here; only the
433
+ /// `unsafe impl Sync` above, to place a `!Sync` `T` in a `static` on this single-core target.
434
+ #[inline]
435
+ pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
436
+ f(&self.0)
437
+ }
438
+ }
439
+
440
+ // ===========================================================================
441
+ // Injectable host hooks (clock + RNG). On the host these are unused (std
442
+ // provides SystemTime / rand); under `portable` the facade (`tishlang_runtime_gba`)
443
+ // installs real sources — agb's timers for the clock, agb's RNG for randomness —
444
+ // and until it does they degrade to deterministic defaults instead of panicking.
445
+ // ===========================================================================
446
+ #[cfg(feature = "portable")]
447
+ mod hooks {
448
+ use super::SingleCore;
449
+ use core::cell::Cell;
450
+
451
+ static NOW_MS: SingleCore<Cell<fn() -> f64>> = SingleCore::new(Cell::new(default_now_ms));
452
+ static RNG_U64: SingleCore<Cell<fn() -> u64>> = SingleCore::new(Cell::new(default_rng));
453
+ static RNG_STATE: SingleCore<Cell<u64>> = SingleCore::new(Cell::new(0x2545_F491_4F6C_DD1D));
454
+
455
+ // Cheap deterministic fallbacks until the facade (tishlang_runtime_gba)
456
+ // installs agb-backed sources.
457
+ fn default_now_ms() -> f64 {
458
+ 0.0
459
+ }
460
+
461
+ fn default_rng() -> u64 {
462
+ // xorshift64* — deterministic PRNG for a target with no entropy source.
463
+ RNG_STATE.with(|c| {
464
+ let mut x = c.get();
465
+ x ^= x >> 12;
466
+ x ^= x << 25;
467
+ x ^= x >> 27;
468
+ c.set(x);
469
+ x.wrapping_mul(0x2545_F491_4F6C_DD1D)
470
+ })
471
+ }
472
+
473
+ /// Facade hook: install a real millisecond clock (e.g. agb timer derived).
474
+ pub fn install_clock(f: fn() -> f64) {
475
+ NOW_MS.with(|c| c.set(f));
476
+ }
477
+
478
+ /// Facade hook: install a real RNG source (e.g. `agb::rng`).
479
+ pub fn install_rng(f: fn() -> u64) {
480
+ RNG_U64.with(|c| c.set(f));
481
+ }
482
+
483
+ /// Facade hook: reseed the fallback xorshift PRNG.
484
+ pub fn seed_rng(seed: u64) {
485
+ RNG_STATE.with(|c| c.set(seed | 1));
486
+ }
487
+
488
+ /// Milliseconds since an arbitrary epoch, for `Date.now()`.
489
+ pub fn now_ms() -> f64 {
490
+ NOW_MS.with(|c| (c.get())())
491
+ }
492
+
493
+ /// A raw random `u64`.
494
+ pub fn next_u64() -> u64 {
495
+ RNG_U64.with(|c| (c.get())())
496
+ }
497
+
498
+ /// A uniform `f64` in [0, 1), for `Math.random()`.
499
+ pub fn random_f64() -> f64 {
500
+ // 53 mantissa bits → [0, 1).
501
+ (next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
502
+ }
503
+ }
504
+
505
+ #[cfg(feature = "portable")]
506
+ pub use hooks::{install_clock, install_rng, next_u64, now_ms, random_f64, seed_rng};
@@ -6,11 +6,20 @@
6
6
  //! `console.log` prints string arguments without surrounding quotes (like Node); nested
7
7
  //! strings inside arrays/objects stay quoted. The REPL still quotes string results for clarity.
8
8
 
9
+ #[cfg(not(feature = "portable"))]
9
10
  use std::io::IsTerminal;
11
+ #[cfg(not(feature = "portable"))]
10
12
  use std::sync::OnceLock;
11
13
 
12
14
  use crate::Value;
15
+ #[cfg(feature = "portable")]
16
+ use alloc::{
17
+ format,
18
+ string::{String, ToString},
19
+ vec::Vec,
20
+ };
13
21
 
22
+ #[cfg(not(feature = "portable"))]
14
23
  static CONSOLE_USES_COLORS: OnceLock<bool> = OnceLock::new();
15
24
 
16
25
  /// ANSI escape codes (standard 4-bit + bright black for dim).
@@ -34,10 +43,18 @@ const SPECIAL: &str = "\x1b[90m";
34
43
  ///
35
44
  /// Cached for the process lifetime. `is_terminal()` is a syscall; benchmarks and
36
45
  /// scripts with many `console.log` calls must not pay it on every line.
46
+ #[cfg(not(feature = "portable"))]
37
47
  pub fn use_console_colors() -> bool {
38
48
  *CONSOLE_USES_COLORS.get_or_init(|| std::io::stdout().is_terminal())
39
49
  }
40
50
 
51
+ /// No ANSI colors under `portable`: `console.log` goes to the mGBA debug log
52
+ /// (`agb::println!`), which is plain text.
53
+ #[cfg(feature = "portable")]
54
+ pub fn use_console_colors() -> bool {
55
+ false
56
+ }
57
+
41
58
  /// Format a single value for console with optional ANSI colors (Node/Bun-style).
42
59
  pub fn format_value_styled(value: &Value, colors: bool) -> String {
43
60
  if !colors {
@@ -86,8 +103,9 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
86
103
  let sep = format!("{PUNCT}, {RESET}");
87
104
  let inner: Vec<String> = arr
88
105
  .borrow()
106
+ .to_values()
89
107
  .iter()
90
- .map(|n| format_value_styled_inner(&Value::Number(*n), colors, true))
108
+ .map(|v| format_value_styled_inner(v, colors, true))
91
109
  .collect();
92
110
  format!("{PUNCT}[{RESET}{}{PUNCT}]{RESET}", inner.join(&sep))
93
111
  }
@@ -1,12 +1,21 @@
1
1
  //! JSON parsing and stringification for Tish values.
2
2
 
3
- use crate::{Value, VmRef};
4
- use std::sync::Arc;
3
+ use crate::compat::{AHashMap, Arc};
4
+ use crate::{NumArrayBacking, Value, VmRef};
5
+ #[cfg(feature = "portable")]
6
+ use crate::compat::FloatExt;
7
+ #[cfg(feature = "portable")]
8
+ use alloc::{
9
+ boxed::Box,
10
+ format,
11
+ string::{String, ToString},
12
+ vec::Vec,
13
+ };
5
14
 
6
15
  /// Per-`json_parse`-call cache of object-key text → shared `Arc<str>`. A JSON array of records
7
16
  /// repeats the same handful of keys across thousands of objects; interning allocates each key
8
17
  /// once and `Arc::clone`s it thereafter, instead of a fresh `Arc<str>` allocation per occurrence.
9
- type KeyCache = ahash::AHashMap<Box<str>, Arc<str>>;
18
+ type KeyCache = AHashMap<Box<str>, Arc<str>>;
10
19
 
11
20
  #[inline]
12
21
  fn intern_key(cache: &mut KeyCache, s: &str) -> Arc<str> {
@@ -133,11 +142,11 @@ fn signal_circular_json_throw() {
133
142
  if !crate::has_pending_throw() {
134
143
  crate::set_pending_throw(Value::object_from_pairs([
135
144
  (
136
- std::sync::Arc::from("name"),
145
+ Arc::from("name"),
137
146
  Value::String("TypeError".into()),
138
147
  ),
139
148
  (
140
- std::sync::Arc::from("message"),
149
+ Arc::from("message"),
141
150
  Value::String("Converting circular structure to JSON".into()),
142
151
  ),
143
152
  ]));
@@ -178,11 +187,24 @@ fn json_stringify_into_guarded(buf: &mut String, value: &Value, ancestors: &mut
178
187
  Value::NumberArray(arr) => {
179
188
  let borrowed = arr.borrow();
180
189
  buf.push('[');
181
- for (i, n) in borrowed.iter().enumerate() {
182
- if i > 0 {
183
- buf.push(',');
190
+ match &*borrowed {
191
+ NumArrayBacking::Packed(nums) => {
192
+ for (i, n) in nums.iter().enumerate() {
193
+ if i > 0 {
194
+ buf.push(',');
195
+ }
196
+ write_json_number(buf, *n);
197
+ }
198
+ }
199
+ // A deopted numeric array serializes element-by-element like a boxed Array.
200
+ NumArrayBacking::Boxed(items) => {
201
+ for (i, item) in items.iter().enumerate() {
202
+ if i > 0 {
203
+ buf.push(',');
204
+ }
205
+ json_stringify_into_guarded(buf, item, ancestors);
206
+ }
184
207
  }
185
- write_json_number(buf, *n);
186
208
  }
187
209
  buf.push(']');
188
210
  }
@@ -253,7 +275,7 @@ pub fn escape_json_string_into(buf: &mut String, s: &str) {
253
275
  b'\x08' => buf.push_str("\\b"),
254
276
  b'\x0c' => buf.push_str("\\f"),
255
277
  _ => {
256
- use std::fmt::Write;
278
+ use core::fmt::Write;
257
279
  let _ = write!(buf, "\\u{:04x}", b as u32);
258
280
  }
259
281
  }
@@ -509,6 +531,7 @@ fn parse_object<'a>(
509
531
  Value::Object(VmRef::new(crate::ObjectData {
510
532
  strings: map,
511
533
  symbols: None,
534
+ frozen: false,
512
535
  })),
513
536
  rest,
514
537
  ));
@@ -539,6 +562,7 @@ fn parse_object<'a>(
539
562
  Value::Object(VmRef::new(crate::ObjectData {
540
563
  strings: map,
541
564
  symbols: None,
565
+ frozen: false,
542
566
  })),
543
567
  &input[1..],
544
568
  ));