@tishlang/tish-lsp 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-lsp +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_lsp/Cargo.toml +3 -1
- 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-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -7,8 +7,25 @@ description = "Shared Value type and utilities for Tish (used by interpreter and
|
|
|
7
7
|
license-file = { workspace = true }
|
|
8
8
|
repository = { workspace = true }
|
|
9
9
|
[features]
|
|
10
|
-
default
|
|
10
|
+
# `std` is on by default, so every existing consumer builds exactly as before
|
|
11
|
+
# (ahash + arcstr + indexmap/std). No workspace crate disables default-features on
|
|
12
|
+
# tishlang_core, so gating the std-only deps here is safe.
|
|
13
|
+
default = ["std"]
|
|
14
|
+
std = ["dep:ahash", "dep:arcstr", "indexmap/std"]
|
|
11
15
|
regex = ["dep:fancy-regex"]
|
|
16
|
+
# no_std + alloc build for embedded targets (Game Boy Advance, thumbv4t-none-eabi).
|
|
17
|
+
# Depend on it as `default-features = false, features = ["portable"]`. It swaps the
|
|
18
|
+
# std-only deps (ahash, arcstr) and primitives (std::sync::Arc/atomic, thread_local!)
|
|
19
|
+
# for atomics-free, alloc-based equivalents via the `compat` module. Default (std)
|
|
20
|
+
# builds are unaffected — `compat` is a transparent re-export when this is off.
|
|
21
|
+
# Mutually exclusive with `send-values` (single-threaded target; no Arc/Mutex).
|
|
22
|
+
# See crates/tish_core/src/compat.rs.
|
|
23
|
+
portable = [
|
|
24
|
+
"dep:hashbrown",
|
|
25
|
+
"dep:rustc-hash",
|
|
26
|
+
"dep:portable-atomic",
|
|
27
|
+
"dep:libm",
|
|
28
|
+
]
|
|
12
29
|
# Make `Value` (and its array / object / regex payloads) `Send + Sync` by
|
|
13
30
|
# switching the interior `Rc<RefCell<T>>` to `Arc<Mutex<T>>` and the native
|
|
14
31
|
# function type from `Rc<dyn Fn>` to `Arc<dyn Fn + Send + Sync>`. Enabled
|
|
@@ -19,10 +36,13 @@ regex = ["dep:fancy-regex"]
|
|
|
19
36
|
send-values = ["dep:parking_lot"]
|
|
20
37
|
|
|
21
38
|
[dependencies]
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
# std-only (need atomics); optional so the `portable` build can drop them.
|
|
40
|
+
ahash = { version = "0.8.11", optional = true }
|
|
41
|
+
arcstr = { version = "1", optional = true }
|
|
24
42
|
smallvec = "1"
|
|
25
|
-
|
|
43
|
+
# default-features=false keeps indexmap no_std-capable; the `std` feature re-adds
|
|
44
|
+
# its std integration for hosted builds (indexmap always works on alloc alone).
|
|
45
|
+
indexmap = { version = "2", default-features = false }
|
|
26
46
|
# Fast integer→decimal for the JSON.stringify number fast-path (already in the workspace lock).
|
|
27
47
|
itoa = "1"
|
|
28
48
|
# Fast shortest-float→decimal for the number→string general path (JSON/template/log). ryu writes the
|
|
@@ -34,5 +54,20 @@ fancy-regex = { version = "0.17.0", optional = true }
|
|
|
34
54
|
# single atomic with no pthread syscall — profiled as a top cost on object/array access.
|
|
35
55
|
parking_lot = { version = "0.12", optional = true }
|
|
36
56
|
|
|
57
|
+
# --- `portable` (no_std) deps. All optional; only pulled in for embedded builds. ---
|
|
58
|
+
# Atomics-free hash map (ahash needs std/atomics). FxHasher is what agb_hashmap uses on GBA.
|
|
59
|
+
hashbrown = { version = "0.15", default-features = false, optional = true }
|
|
60
|
+
rustc-hash = { version = "2", default-features = false, optional = true }
|
|
61
|
+
# AtomicU64 for the single symbol-id counter — portable-atomic provides it on no-atomics
|
|
62
|
+
# targets via critical-section (impl supplied at final link by agb).
|
|
63
|
+
# Default features include `fallback`, which provides AtomicU64 on targets lacking
|
|
64
|
+
# native 64-bit atomics. The single-core backend is selected by the
|
|
65
|
+
# `portable_atomic_unsafe_assume_single_core` cfg — supplied by agb in the real GBA
|
|
66
|
+
# build (do NOT also enable the `critical-section` feature; agb forbids both). The
|
|
67
|
+
# standalone core/builtins CI build passes that cfg via RUSTFLAGS.
|
|
68
|
+
portable-atomic = { version = "1", optional = true }
|
|
69
|
+
# f64 transcendentals in no_std (std's f64 methods are unavailable without libm).
|
|
70
|
+
libm = { version = "0.2", optional = true }
|
|
71
|
+
|
|
37
72
|
[target.wasm32-unknown-unknown.dependencies]
|
|
38
73
|
getrandom = { version = "0.3", features = ["wasm_js"] }
|
|
@@ -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 {
|
|
@@ -73,6 +90,10 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
|
|
|
73
90
|
}
|
|
74
91
|
Value::Bool(b) => format!("{BOOLEAN}{b}{RESET}"),
|
|
75
92
|
Value::Null => format!("{NULL}null{RESET}"),
|
|
93
|
+
#[cfg(feature = "portable")]
|
|
94
|
+
Value::Struct(s) => {
|
|
95
|
+
format_value_styled_inner(&s.borrow().tish_to_object(), colors, quote_strings)
|
|
96
|
+
}
|
|
76
97
|
Value::Array(arr) => {
|
|
77
98
|
let inner: Vec<String> = arr
|
|
78
99
|
.borrow()
|
|
@@ -86,8 +107,9 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
|
|
|
86
107
|
let sep = format!("{PUNCT}, {RESET}");
|
|
87
108
|
let inner: Vec<String> = arr
|
|
88
109
|
.borrow()
|
|
110
|
+
.to_values()
|
|
89
111
|
.iter()
|
|
90
|
-
.map(|
|
|
112
|
+
.map(|v| format_value_styled_inner(v, colors, true))
|
|
91
113
|
.collect();
|
|
92
114
|
format!("{PUNCT}[{RESET}{}{PUNCT}]{RESET}", inner.join(&sep))
|
|
93
115
|
}
|