@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.
Files changed (74) hide show
  1. package/Cargo.toml +4 -1
  2. package/bin/tish-lint +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_lint/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-lint +0 -0
  71. package/platform/darwin-x64/tish-lint +0 -0
  72. package/platform/linux-arm64/tish-lint +0 -0
  73. package/platform/linux-x64/tish-lint +0 -0
  74. package/platform/win32-x64/tish-lint.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;