@tishlang/tish-format 2.38.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-format +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 +812 -47
- 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_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/http_hyper.rs +70 -0
- package/crates/tish_runtime/src/lib.rs +55 -10
- package/crates/tish_runtime/src/ws.rs +94 -21
- 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-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
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
//! no_std runtime facade for Tish compiled output on the Game Boy Advance.
|
|
2
|
+
//!
|
|
3
|
+
//! The `Gba` emit mode generates a crate that depends on this one under the name
|
|
4
|
+
//! `tishlang_runtime` (via a Cargo `package =` rename), so every emitted
|
|
5
|
+
//! `tishlang_runtime::…` path resolves here. It re-exports the portable prelude
|
|
6
|
+
//! surface from `tishlang_core` / `tishlang_builtins` and adds the GBA runtime
|
|
7
|
+
//! entry points in [`gba`].
|
|
8
|
+
#![no_std]
|
|
9
|
+
|
|
10
|
+
extern crate alloc;
|
|
11
|
+
|
|
12
|
+
use alloc::boxed::Box;
|
|
13
|
+
use alloc::format;
|
|
14
|
+
use alloc::string::{String, ToString};
|
|
15
|
+
use alloc::vec::Vec;
|
|
16
|
+
|
|
17
|
+
// ── Core value vocabulary (single source of truth in tishlang_core) ──────────
|
|
18
|
+
pub use tishlang_core::{
|
|
19
|
+
js_number_to_string_into, to_int32, to_int32_value, to_number_value, to_uint32,
|
|
20
|
+
to_uint32_value, ArcStr, NumArrayBacking, ObjectData, ObjectMap, PropMap, Value, VmReadGuard,
|
|
21
|
+
VmRef, VmWriteGuard,
|
|
22
|
+
};
|
|
23
|
+
/// `Arc` on GBA is `Rc` (single-core). Emitted code writes `Arc::from(..)`.
|
|
24
|
+
pub use tishlang_core::Arc;
|
|
25
|
+
/// Fixed-point scalar the typed-numeric path lowers `fixed` to (agb `Num<i32,8>`).
|
|
26
|
+
pub type Fixed = agb::fixnum::Num<i32, 8>;
|
|
27
|
+
|
|
28
|
+
// ── Pending-throw + recursion-guard plumbing (shared slot in tishlang_core) ──
|
|
29
|
+
pub use tishlang_core::{
|
|
30
|
+
has_pending_throw, set_pending_throw, stack_overflow_error, take_pending_throw, CallDepthGuard,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/// Enter a boxed user-fn call frame or trip the recursion guard. On GBA there is
|
|
34
|
+
/// no stack-pressure probe (no `std`), so this is just the counted-depth guard.
|
|
35
|
+
#[inline]
|
|
36
|
+
pub fn enter_call_guarded() -> Option<CallDepthGuard> {
|
|
37
|
+
tishlang_core::enter_call_guarded()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Error type for throw/return non-local control flow ───────────────────────
|
|
41
|
+
/// Mirrors `tishlang_runtime::TishError` (the host runtime defines its own; kept
|
|
42
|
+
/// in lock-step). `run()` in generated code returns `Result<(), Box<dyn Error>>`.
|
|
43
|
+
#[derive(Debug, Clone)]
|
|
44
|
+
pub enum TishError {
|
|
45
|
+
Throw(Value),
|
|
46
|
+
Return(Value),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl core::fmt::Display for TishError {
|
|
50
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
51
|
+
match self {
|
|
52
|
+
TishError::Throw(v) => write!(f, "{}", v.to_display_string()),
|
|
53
|
+
TishError::Return(v) => write!(f, "return {}", v.to_display_string()),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl core::error::Error for TishError {}
|
|
59
|
+
|
|
60
|
+
/// Convert a boxed error thrown out of `run()` back into a `Value` (throw/return payload).
|
|
61
|
+
pub fn fn_unwind(e: Box<dyn core::error::Error>) -> Value {
|
|
62
|
+
if let Some(te) = e.downcast_ref::<TishError>() {
|
|
63
|
+
match te {
|
|
64
|
+
TishError::Throw(v) | TishError::Return(v) => return v.clone(),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
Value::String(e.to_string().into())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Value call / concat / read helpers used by native codegen ────────────────
|
|
71
|
+
/// `f()` / `obj()` dispatch, suppressed while a throw is unwinding (#381).
|
|
72
|
+
pub fn value_call(callee: &Value, args: &[Value]) -> Value {
|
|
73
|
+
if has_pending_throw() {
|
|
74
|
+
return Value::Null;
|
|
75
|
+
}
|
|
76
|
+
tishlang_core::value_call(callee, args)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Append `v`'s JS string-concat form to `buf` (no throwaway `String`).
|
|
80
|
+
#[inline]
|
|
81
|
+
pub fn push_value_str(buf: &mut String, v: &Value) {
|
|
82
|
+
match v {
|
|
83
|
+
Value::String(s) => buf.push_str(s),
|
|
84
|
+
Value::Number(n) => js_number_to_string_into(buf, *n),
|
|
85
|
+
Value::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
|
|
86
|
+
Value::Null => buf.push_str("null"),
|
|
87
|
+
other => buf.push_str(&other.to_js_string()),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Read a captured `VmRef` cell, releasing the guard before returning (#218).
|
|
92
|
+
#[inline]
|
|
93
|
+
pub fn vm_read<T: Clone>(cell: &VmRef<T>) -> T {
|
|
94
|
+
(*cell.borrow()).clone()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// `for…of` iterable normalization for the native backend.
|
|
98
|
+
pub fn normalize_for_of(v: Value) -> Value {
|
|
99
|
+
if let Value::NumberArray(arr) = &v {
|
|
100
|
+
let items: Vec<Value> = arr.borrow().to_values();
|
|
101
|
+
return Value::Array(VmRef::new(items));
|
|
102
|
+
}
|
|
103
|
+
match tishlang_core::drain_iterator(&v) {
|
|
104
|
+
Some(items) => Value::Array(VmRef::new(items)),
|
|
105
|
+
None => v,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// JS `in` operator (`key in obj`).
|
|
110
|
+
pub fn in_operator(key: &Value, obj: &Value) -> Value {
|
|
111
|
+
match obj {
|
|
112
|
+
Value::Object(_) => Value::Bool(tishlang_core::object_has(obj, key)),
|
|
113
|
+
Value::Array(arr) => Value::Bool(array_in(key, arr.borrow().len())),
|
|
114
|
+
Value::NumberArray(arr) => Value::Bool(array_in(key, arr.borrow().len())),
|
|
115
|
+
_ => Value::Bool(false),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn array_in(key: &Value, len: usize) -> bool {
|
|
120
|
+
let key_str: Arc<str> = match key {
|
|
121
|
+
Value::String(s) => Arc::from(s.as_str()),
|
|
122
|
+
Value::Number(n) => n.to_string().into(),
|
|
123
|
+
_ => return false,
|
|
124
|
+
};
|
|
125
|
+
key_str.as_ref() == "length"
|
|
126
|
+
|| key_str.parse::<usize>().ok().map(|i| i < len).unwrap_or(false)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Member / index read + write (ported from the host runtime; the regex/promise
|
|
130
|
+
// arms are feature-gated off on GBA). Method dispatch (`arr.map(f)`) is emitted
|
|
131
|
+
// as direct builtin calls by codegen, not through these. ──────────────────────
|
|
132
|
+
use tishlang_builtins::collections::collection_size;
|
|
133
|
+
use tishlang_builtins::helpers::extract_num;
|
|
134
|
+
|
|
135
|
+
pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
|
|
136
|
+
let key = key.as_ref();
|
|
137
|
+
match obj {
|
|
138
|
+
Value::Object(map) => {
|
|
139
|
+
if key == "size" {
|
|
140
|
+
if let Some(n) = collection_size(obj) {
|
|
141
|
+
return Value::Number(n);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
map.borrow().strings.get(key).cloned().unwrap_or(Value::Null)
|
|
145
|
+
}
|
|
146
|
+
Value::Array(arr) => {
|
|
147
|
+
if key == "length" {
|
|
148
|
+
Value::Number(arr.borrow().len() as f64)
|
|
149
|
+
} else if let Ok(idx) = key.parse::<usize>() {
|
|
150
|
+
arr.borrow().get(idx).cloned().unwrap_or(Value::Null)
|
|
151
|
+
} else {
|
|
152
|
+
Value::Null
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
Value::NumberArray(arr) => {
|
|
156
|
+
if key == "length" {
|
|
157
|
+
Value::Number(arr.borrow().len() as f64)
|
|
158
|
+
} else if let Ok(idx) = key.parse::<usize>() {
|
|
159
|
+
arr.borrow()
|
|
160
|
+
.get(idx)
|
|
161
|
+
.map(|v| match v {
|
|
162
|
+
Value::Number(n) if n.is_nan() => Value::Null,
|
|
163
|
+
other => other,
|
|
164
|
+
})
|
|
165
|
+
.unwrap_or(Value::Null)
|
|
166
|
+
} else {
|
|
167
|
+
Value::Null
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
Value::String(s) => {
|
|
171
|
+
if key == "length" {
|
|
172
|
+
Value::Number(tishlang_builtins::string::char_count(s) as f64)
|
|
173
|
+
} else {
|
|
174
|
+
Value::Null
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
Value::Opaque(o) => o.get_method(key).map(Value::Function).unwrap_or(Value::Null),
|
|
178
|
+
Value::Null => {
|
|
179
|
+
tishlang_core::set_pending_throw(tishlang_core::cannot_read_property_error(key));
|
|
180
|
+
Value::Null
|
|
181
|
+
}
|
|
182
|
+
_ => Value::Null,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pub fn get_index(obj: &Value, index: &Value) -> Value {
|
|
187
|
+
match obj {
|
|
188
|
+
Value::Array(arr) => {
|
|
189
|
+
let idx = match index {
|
|
190
|
+
Value::Number(n) => *n as usize,
|
|
191
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
192
|
+
Some(i) => i,
|
|
193
|
+
None => return Value::Null,
|
|
194
|
+
},
|
|
195
|
+
_ => return Value::Null,
|
|
196
|
+
};
|
|
197
|
+
arr.borrow().get(idx).cloned().unwrap_or(Value::Null)
|
|
198
|
+
}
|
|
199
|
+
Value::NumberArray(arr) => {
|
|
200
|
+
let idx = match index {
|
|
201
|
+
Value::Number(n) => *n as usize,
|
|
202
|
+
Value::String(s) => match tishlang_core::str_to_array_index(s) {
|
|
203
|
+
Some(i) => i,
|
|
204
|
+
None => return Value::Null,
|
|
205
|
+
},
|
|
206
|
+
_ => return Value::Null,
|
|
207
|
+
};
|
|
208
|
+
arr.borrow()
|
|
209
|
+
.get(idx)
|
|
210
|
+
.map(|v| match v {
|
|
211
|
+
Value::Number(n) if n.is_nan() => Value::Null,
|
|
212
|
+
other => other,
|
|
213
|
+
})
|
|
214
|
+
.unwrap_or(Value::Null)
|
|
215
|
+
}
|
|
216
|
+
Value::String(s) => match index {
|
|
217
|
+
Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => {
|
|
218
|
+
tishlang_builtins::string::nth_char(s, *n as usize)
|
|
219
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
220
|
+
.unwrap_or(Value::Null)
|
|
221
|
+
}
|
|
222
|
+
_ => Value::Null,
|
|
223
|
+
},
|
|
224
|
+
Value::Object(_) => tishlang_core::object_get(obj, index).unwrap_or(Value::Null),
|
|
225
|
+
Value::Null => {
|
|
226
|
+
let key = match index {
|
|
227
|
+
Value::String(s) => s.to_string(),
|
|
228
|
+
other => other.to_js_string(),
|
|
229
|
+
};
|
|
230
|
+
tishlang_core::set_pending_throw(tishlang_core::cannot_read_property_error(&key));
|
|
231
|
+
Value::Null
|
|
232
|
+
}
|
|
233
|
+
_ => Value::Null,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
pub fn delete_property(obj: &Value, key: &Value) -> Value {
|
|
238
|
+
match obj {
|
|
239
|
+
Value::Object(m) => {
|
|
240
|
+
let key_s = match key {
|
|
241
|
+
Value::String(s) => s.to_string(),
|
|
242
|
+
other => other.to_js_string(),
|
|
243
|
+
};
|
|
244
|
+
m.borrow_mut().strings.remove(key_s.as_str());
|
|
245
|
+
}
|
|
246
|
+
Value::Array(arr) => {
|
|
247
|
+
if let Value::Number(n) = key {
|
|
248
|
+
let n = *n;
|
|
249
|
+
if n >= 0.0 && n.fract() == 0.0 {
|
|
250
|
+
let i = n as usize;
|
|
251
|
+
let mut a = arr.borrow_mut();
|
|
252
|
+
if i < a.len() {
|
|
253
|
+
a[i] = Value::Null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
_ => {}
|
|
259
|
+
}
|
|
260
|
+
Value::Bool(true)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// A valid JS array index: a non-negative integer `< 2^32-1`. Anything else (negative,
|
|
264
|
+
/// fractional, NaN, or too large) is `None` — on GBA we treat such an assignment as a no-op
|
|
265
|
+
/// rather than densifying/aborting (JS would set a named property, which a `Vec`-backed array
|
|
266
|
+
/// can't hold anyway).
|
|
267
|
+
fn array_index(n: f64) -> Option<usize> {
|
|
268
|
+
if n >= 0.0 && n.fract() == 0.0 && n <= 4_294_967_294.0 {
|
|
269
|
+
Some(n as usize)
|
|
270
|
+
} else {
|
|
271
|
+
None
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// Grow `v` so `index` is in range, filling with `fill`. Instead of aborting when the
|
|
276
|
+
/// allocation can't be satisfied (a real risk on the GBA's 256KB EWRAM for a large sparse
|
|
277
|
+
/// index), it raises a CATCHABLE tish error and returns `false` so the caller bails out.
|
|
278
|
+
fn grow_or_throw<T: Clone>(v: &mut alloc::vec::Vec<T>, index: usize, fill: T) -> bool {
|
|
279
|
+
if index >= v.len() {
|
|
280
|
+
let extra = index - v.len() + 1;
|
|
281
|
+
if v.try_reserve(extra).is_err() {
|
|
282
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
|
|
283
|
+
"array index {index} too large to allocate"
|
|
284
|
+
)));
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
v.resize(index + 1, fill);
|
|
288
|
+
}
|
|
289
|
+
true
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
pub fn set_prop(obj: &Value, key: &str, val: Value) -> Value {
|
|
293
|
+
match obj {
|
|
294
|
+
Value::Object(map) => {
|
|
295
|
+
let mut m = map.borrow_mut();
|
|
296
|
+
if m.frozen {
|
|
297
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
|
|
298
|
+
"Cannot assign to read only property '{key}' of a frozen object"
|
|
299
|
+
)));
|
|
300
|
+
return val;
|
|
301
|
+
}
|
|
302
|
+
if let Some(slot) = m.strings.get_mut(key) {
|
|
303
|
+
*slot = val.clone();
|
|
304
|
+
} else {
|
|
305
|
+
m.strings.insert(Arc::from(key), val.clone());
|
|
306
|
+
}
|
|
307
|
+
val
|
|
308
|
+
}
|
|
309
|
+
Value::Array(arr) if key == "length" => {
|
|
310
|
+
let n = extract_num(Some(&val)).unwrap_or(f64::NAN);
|
|
311
|
+
if n.is_nan() || n < 0.0 || n.fract() != 0.0 || n > 4_294_967_295.0 {
|
|
312
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error("Invalid array length"));
|
|
313
|
+
return val;
|
|
314
|
+
}
|
|
315
|
+
let len = n as usize;
|
|
316
|
+
let mut arr_mut = arr.borrow_mut();
|
|
317
|
+
if len > arr_mut.len() {
|
|
318
|
+
// grow_or_throw indexes to len-1 (so len is in range); catchable on OOM.
|
|
319
|
+
let _ = grow_or_throw(&mut arr_mut, len - 1, Value::Null);
|
|
320
|
+
} else {
|
|
321
|
+
arr_mut.truncate(len);
|
|
322
|
+
}
|
|
323
|
+
val
|
|
324
|
+
}
|
|
325
|
+
_ => {
|
|
326
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error("Cannot assign property on a non-object"));
|
|
327
|
+
val
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
|
|
333
|
+
// Resolve the array index, or `None` for a non-index key (negative/fractional/huge number,
|
|
334
|
+
// non-numeric string, or non-string/number key). A `None` on an array is a no-op here
|
|
335
|
+
// rather than an abort: JS would set a named property, which a `Vec`-backed array can't hold.
|
|
336
|
+
let arr_index = |idx: &Value| -> Option<usize> {
|
|
337
|
+
match idx {
|
|
338
|
+
Value::Number(n) => array_index(*n),
|
|
339
|
+
Value::String(s) => tishlang_core::str_to_array_index(s),
|
|
340
|
+
_ => None,
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
match obj {
|
|
344
|
+
Value::Array(arr) => {
|
|
345
|
+
let Some(index) = arr_index(idx) else {
|
|
346
|
+
return val;
|
|
347
|
+
};
|
|
348
|
+
let mut arr_mut = arr.borrow_mut();
|
|
349
|
+
if !grow_or_throw(&mut arr_mut, index, Value::Null) {
|
|
350
|
+
return val;
|
|
351
|
+
}
|
|
352
|
+
arr_mut[index] = val.clone();
|
|
353
|
+
val
|
|
354
|
+
}
|
|
355
|
+
Value::NumberArray(arr) => {
|
|
356
|
+
let Some(index) = arr_index(idx) else {
|
|
357
|
+
return val;
|
|
358
|
+
};
|
|
359
|
+
let mut b = arr.borrow_mut();
|
|
360
|
+
match (b.as_packed_mut(), val.as_number()) {
|
|
361
|
+
(Some(nums), Some(n)) => {
|
|
362
|
+
if grow_or_throw(nums, index, f64::NAN) {
|
|
363
|
+
nums[index] = n;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
_ => {
|
|
367
|
+
let boxed = b.deopt();
|
|
368
|
+
if grow_or_throw(boxed, index, Value::Null) {
|
|
369
|
+
boxed[index] = val.clone();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
val
|
|
374
|
+
}
|
|
375
|
+
Value::Object(map) => {
|
|
376
|
+
if map.borrow().frozen {
|
|
377
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
|
|
378
|
+
"Cannot assign to read only property '{}' of a frozen object",
|
|
379
|
+
idx.to_display_string()
|
|
380
|
+
)));
|
|
381
|
+
return val;
|
|
382
|
+
}
|
|
383
|
+
// object_set only accepts string/number/symbol keys; on any other key raise a
|
|
384
|
+
// CATCHABLE error rather than aborting (`.expect`) the console.
|
|
385
|
+
if tishlang_core::object_set(obj, idx, val.clone()).is_err() {
|
|
386
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
|
|
387
|
+
"cannot use {} as an object key",
|
|
388
|
+
idx.to_display_string()
|
|
389
|
+
)));
|
|
390
|
+
}
|
|
391
|
+
val
|
|
392
|
+
}
|
|
393
|
+
_ => {
|
|
394
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error("Cannot index-assign on a non-array/object"));
|
|
395
|
+
val
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── console.* → mGBA debug log (agb::println!) ───────────────────────────────
|
|
401
|
+
fn console_line(args: &[Value]) -> String {
|
|
402
|
+
tishlang_core::format_values_for_console(args, false)
|
|
403
|
+
}
|
|
404
|
+
pub fn console_log(args: &[Value]) {
|
|
405
|
+
agb::println!("{}", console_line(args));
|
|
406
|
+
}
|
|
407
|
+
pub fn console_info(args: &[Value]) {
|
|
408
|
+
agb::println!("{}", console_line(args));
|
|
409
|
+
}
|
|
410
|
+
pub fn console_debug(args: &[Value]) {
|
|
411
|
+
agb::println!("{}", console_line(args));
|
|
412
|
+
}
|
|
413
|
+
pub fn console_warn(args: &[Value]) {
|
|
414
|
+
agb::println!("{}", console_line(args));
|
|
415
|
+
}
|
|
416
|
+
pub fn console_error(args: &[Value]) {
|
|
417
|
+
agb::println!("{}", console_line(args));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ── JSON (native-ABI wrappers over the core `&str`→Result / `&Value`→String fns) ──
|
|
421
|
+
pub fn json_parse(args: &[Value]) -> Value {
|
|
422
|
+
let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
|
|
423
|
+
tishlang_core::json_parse(&s).unwrap_or(Value::Null)
|
|
424
|
+
}
|
|
425
|
+
pub fn json_stringify(args: &[Value]) -> Value {
|
|
426
|
+
let v = args.first().cloned().unwrap_or(Value::Null);
|
|
427
|
+
Value::String(tishlang_core::json_stringify(&v).into())
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// Build an `ObjectMap` from an array of key/value pairs. Generated object literals
|
|
431
|
+
/// emit `ObjectMap::from([...])`; hashbrown's `From<[_; N]>` isn't available with a
|
|
432
|
+
/// custom hasher, so the Gba post-pass rewrites those calls to this.
|
|
433
|
+
pub fn object_map_from<const N: usize>(items: [(Arc<str>, Value); N]) -> ObjectMap {
|
|
434
|
+
let mut m = ObjectMap::default();
|
|
435
|
+
for (k, v) in items {
|
|
436
|
+
m.insert(k, v);
|
|
437
|
+
}
|
|
438
|
+
m
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// f64 transcendentals for generated inline math (`Math.sqrt(x)` const-folds to
|
|
442
|
+
/// `x.sqrt()`); re-exported so generated code can `use tishlang_runtime::FloatExt`.
|
|
443
|
+
pub use tishlang_core::FloatExt;
|
|
444
|
+
|
|
445
|
+
/// Single-core interior-mutable static helper, re-exported for binding crates
|
|
446
|
+
/// (`tish-agb`) that keep hardware context in a `static`.
|
|
447
|
+
pub use tishlang_core::SingleCore;
|
|
448
|
+
|
|
449
|
+
/// Value arithmetic / comparison operators for generated `+`,`-`,… (ported from the
|
|
450
|
+
/// host runtime's `ops`; pure `Value` math, no_std-safe).
|
|
451
|
+
pub mod ops {
|
|
452
|
+
use crate::Value;
|
|
453
|
+
use alloc::string::String;
|
|
454
|
+
|
|
455
|
+
#[inline]
|
|
456
|
+
pub fn add(left: &Value, right: &Value) -> Value {
|
|
457
|
+
match (left, right) {
|
|
458
|
+
(Value::Number(a), Value::Number(b)) => Value::Number(a + b),
|
|
459
|
+
(Value::String(a), Value::String(b)) => {
|
|
460
|
+
let mut s = String::with_capacity(a.len() + b.len());
|
|
461
|
+
s.push_str(a);
|
|
462
|
+
s.push_str(b);
|
|
463
|
+
Value::String(s.into())
|
|
464
|
+
}
|
|
465
|
+
(Value::String(a), b) => {
|
|
466
|
+
let b_str = b.to_js_string();
|
|
467
|
+
let mut s = String::with_capacity(a.len() + b_str.len());
|
|
468
|
+
s.push_str(a);
|
|
469
|
+
s.push_str(&b_str);
|
|
470
|
+
Value::String(s.into())
|
|
471
|
+
}
|
|
472
|
+
(a, Value::String(b)) => {
|
|
473
|
+
let a_str = a.to_js_string();
|
|
474
|
+
let mut s = String::with_capacity(a_str.len() + b.len());
|
|
475
|
+
s.push_str(&a_str);
|
|
476
|
+
s.push_str(b);
|
|
477
|
+
Value::String(s.into())
|
|
478
|
+
}
|
|
479
|
+
(a, b) => Value::Number(
|
|
480
|
+
a.as_number().unwrap_or(f64::NAN) + b.as_number().unwrap_or(f64::NAN),
|
|
481
|
+
),
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
macro_rules! num_op {
|
|
486
|
+
($name:ident, $op:tt) => {
|
|
487
|
+
#[inline]
|
|
488
|
+
pub fn $name(left: &Value, right: &Value) -> Value {
|
|
489
|
+
match (left, right) {
|
|
490
|
+
(Value::Number(a), Value::Number(b)) => Value::Number(a $op b),
|
|
491
|
+
(a, b) => Value::Number(
|
|
492
|
+
a.as_number().unwrap_or(f64::NAN) $op b.as_number().unwrap_or(f64::NAN),
|
|
493
|
+
),
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
num_op!(sub, -);
|
|
499
|
+
num_op!(mul, *);
|
|
500
|
+
num_op!(div, /);
|
|
501
|
+
num_op!(modulo, %);
|
|
502
|
+
|
|
503
|
+
macro_rules! cmp_op {
|
|
504
|
+
($name:ident, $op:tt) => {
|
|
505
|
+
#[inline]
|
|
506
|
+
pub fn $name(left: &Value, right: &Value) -> Value {
|
|
507
|
+
let b = match (left, right) {
|
|
508
|
+
(Value::Number(a), Value::Number(b)) => a $op b,
|
|
509
|
+
(Value::String(a), Value::String(b)) => a.as_str() $op b.as_str(),
|
|
510
|
+
_ => false,
|
|
511
|
+
};
|
|
512
|
+
Value::Bool(b)
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
cmp_op!(lt, <);
|
|
517
|
+
cmp_op!(le, <=);
|
|
518
|
+
cmp_op!(gt, >);
|
|
519
|
+
cmp_op!(ge, >=);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ── Globals / object / number / string / uri (all live in builtins::globals) ─
|
|
523
|
+
pub use tishlang_builtins::globals::{
|
|
524
|
+
array_from, array_is_array, array_of, boolean, decode_uri, decode_uri_component, encode_uri,
|
|
525
|
+
encode_uri_component, is_finite, is_nan, number_convert, number_is_finite, number_is_integer,
|
|
526
|
+
number_is_nan, number_is_safe_integer, object_assign, object_entries, object_freeze,
|
|
527
|
+
object_from_entries, object_has_own, object_is, object_is_frozen, object_keys, object_values,
|
|
528
|
+
parse_float, parse_int, string_convert, string_from_char_code, structured_clone,
|
|
529
|
+
};
|
|
530
|
+
pub use tishlang_builtins::string::escape_html as string_escape_html_impl;
|
|
531
|
+
|
|
532
|
+
// ── Constructors, collections, typed arrays, symbol (re-export as prelude names) ──
|
|
533
|
+
pub use tishlang_builtins::construct::{
|
|
534
|
+
array_construct, audio_context_constructor_value as tish_audio_context_constructor,
|
|
535
|
+
construct as tish_construct, error_constructor_value as tish_error_constructor,
|
|
536
|
+
};
|
|
537
|
+
pub use tishlang_builtins::date::date_constructor_value as tish_date_constructor;
|
|
538
|
+
pub use tishlang_builtins::collections::{
|
|
539
|
+
map_constructor_value as tish_map_constructor, map_get, map_has, map_set, map_values,
|
|
540
|
+
set_constructor_value as tish_set_constructor,
|
|
541
|
+
};
|
|
542
|
+
// Array mutators (portable) — `.push()` / `.pop()` lower to these `array_*` names.
|
|
543
|
+
pub use tishlang_builtins::array::{pop as array_pop, push as array_push};
|
|
544
|
+
pub use tishlang_builtins::symbol::symbol_object;
|
|
545
|
+
pub use tishlang_builtins::typedarrays::{
|
|
546
|
+
float32_array_constructor_value as tish_float32_array_constructor,
|
|
547
|
+
float64_array_constructor_value as tish_float64_array_constructor,
|
|
548
|
+
int16_array_constructor_value as tish_int16_array_constructor,
|
|
549
|
+
int32_array_constructor_value as tish_int32_array_constructor,
|
|
550
|
+
int8_array_constructor_value as tish_int8_array_constructor,
|
|
551
|
+
uint16_array_constructor_value as tish_uint16_array_constructor,
|
|
552
|
+
uint32_array_constructor_value as tish_uint32_array_constructor,
|
|
553
|
+
uint8_array_constructor_value as tish_uint8_array_constructor,
|
|
554
|
+
uint8_clamped_array_constructor_value as tish_uint8_clamped_array_constructor,
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// ── Math (thin wrappers preserving the `math_*` prelude naming) ──────────────
|
|
558
|
+
macro_rules! math_fwd {
|
|
559
|
+
($($name:ident => $path:path),* $(,)?) => {
|
|
560
|
+
$(
|
|
561
|
+
#[inline]
|
|
562
|
+
pub fn $name(args: &[Value]) -> Value { $path(args) }
|
|
563
|
+
)*
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
math_fwd! {
|
|
567
|
+
math_abs => tishlang_builtins::math::abs,
|
|
568
|
+
math_ceil => tishlang_builtins::math::ceil,
|
|
569
|
+
math_floor => tishlang_builtins::math::floor,
|
|
570
|
+
math_round => tishlang_builtins::math::round,
|
|
571
|
+
math_sqrt => tishlang_builtins::math::sqrt,
|
|
572
|
+
math_sin => tishlang_builtins::math::sin,
|
|
573
|
+
math_cos => tishlang_builtins::math::cos,
|
|
574
|
+
math_tan => tishlang_builtins::math::tan,
|
|
575
|
+
math_asin => tishlang_builtins::math::asin,
|
|
576
|
+
math_acos => tishlang_builtins::math::acos,
|
|
577
|
+
math_atan => tishlang_builtins::math::atan,
|
|
578
|
+
math_atan2 => tishlang_builtins::math::atan2,
|
|
579
|
+
math_log => tishlang_builtins::math::log,
|
|
580
|
+
math_log2 => tishlang_builtins::math::log2,
|
|
581
|
+
math_log10 => tishlang_builtins::math::log10,
|
|
582
|
+
math_exp => tishlang_builtins::math::exp,
|
|
583
|
+
math_expm1 => tishlang_builtins::math::expm1,
|
|
584
|
+
math_log1p => tishlang_builtins::math::log1p,
|
|
585
|
+
math_cbrt => tishlang_builtins::math::cbrt,
|
|
586
|
+
math_trunc => tishlang_builtins::math::trunc,
|
|
587
|
+
math_sign => tishlang_builtins::math::sign,
|
|
588
|
+
math_pow => tishlang_builtins::math::pow,
|
|
589
|
+
math_max => tishlang_builtins::math::max,
|
|
590
|
+
math_min => tishlang_builtins::math::min,
|
|
591
|
+
math_hypot => tishlang_builtins::math::hypot,
|
|
592
|
+
math_imul => tishlang_builtins::math::imul,
|
|
593
|
+
math_clz32 => tishlang_builtins::math::clz32,
|
|
594
|
+
math_fround => tishlang_builtins::math::fround,
|
|
595
|
+
math_random => tishlang_builtins::math::random,
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Hyperbolic + inverse-hyperbolic: not in builtins::math; computed via FloatExt (libm,
|
|
599
|
+
// already in scope from the `pub use` above).
|
|
600
|
+
macro_rules! math_unary_libm {
|
|
601
|
+
($($name:ident => $m:ident),* $(,)?) => {
|
|
602
|
+
$(
|
|
603
|
+
#[inline]
|
|
604
|
+
pub fn $name(args: &[Value]) -> Value {
|
|
605
|
+
let n = match args.first() { Some(Value::Number(n)) => *n, _ => f64::NAN };
|
|
606
|
+
Value::Number(n.$m())
|
|
607
|
+
}
|
|
608
|
+
)*
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
math_unary_libm! {
|
|
612
|
+
math_sinh => sinh,
|
|
613
|
+
math_cosh => cosh,
|
|
614
|
+
math_tanh => tanh,
|
|
615
|
+
math_asinh => asinh,
|
|
616
|
+
math_acosh => acosh,
|
|
617
|
+
math_atanh => atanh,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
pub mod gba;
|