@tishlang/tish-lsp 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.
Files changed (75) hide show
  1. package/Cargo.toml +4 -1
  2. package/bin/tish-lsp +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 +812 -47
  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_lsp/Cargo.toml +3 -1
  53. package/crates/tish_lsp/src/import_goto.rs +41 -14
  54. package/crates/tish_lsp/src/main.rs +18 -0
  55. package/crates/tish_native/src/build.rs +246 -0
  56. package/crates/tish_native/src/config.rs +20 -0
  57. package/crates/tish_native/src/lib.rs +16 -6
  58. package/crates/tish_parser/src/lib.rs +28 -0
  59. package/crates/tish_parser/src/parser.rs +25 -4
  60. package/crates/tish_runtime/src/http_hyper.rs +70 -0
  61. package/crates/tish_runtime/src/lib.rs +55 -10
  62. package/crates/tish_runtime/src/ws.rs +94 -21
  63. package/crates/tish_runtime_gba/Cargo.lock +837 -0
  64. package/crates/tish_runtime_gba/Cargo.toml +16 -0
  65. package/crates/tish_runtime_gba/src/gba.rs +211 -0
  66. package/crates/tish_runtime_gba/src/lib.rs +620 -0
  67. package/crates/tish_vm/src/jit.rs +1446 -164
  68. package/crates/tish_vm/src/vm.rs +535 -173
  69. package/justfile +9 -0
  70. package/package.json +1 -1
  71. package/platform/darwin-arm64/tish-lsp +0 -0
  72. package/platform/darwin-x64/tish-lsp +0 -0
  73. package/platform/linux-arm64/tish-lsp +0 -0
  74. package/platform/linux-x64/tish-lsp +0 -0
  75. package/platform/win32-x64/tish-lsp.exe +0 -0
@@ -16,6 +16,7 @@ pub use tishlang_core::ObjectMap;
16
16
  // pass) instead of materializing an intermediate `AHashMap` and rebuilding from it.
17
17
  pub use tishlang_core::{ObjectData, PropMap};
18
18
  pub use tishlang_core::Value;
19
+ pub use tishlang_core::NumArrayBacking;
19
20
  pub use tishlang_core::ArcStr;
20
21
  /// Used by native codegen for `f()` / `obj()` dispatch (`Value::Function` or `__call` on objects).
21
22
  /// Wraps `tishlang_core::value_call` with a pending-throw suppression check (#381): while a thrown
@@ -79,7 +80,7 @@ pub fn normalize_for_of(v: Value) -> Value {
79
80
  // single-variant `if let Value::Array` in the spread / for-of codegen binds — otherwise it spreads
80
81
  // to zero elements.
81
82
  if let Value::NumberArray(arr) = &v {
82
- let items: Vec<Value> = arr.borrow().iter().map(|n| Value::Number(*n)).collect();
83
+ let items: Vec<Value> = arr.borrow().to_values();
83
84
  return Value::Array(VmRef::new(items));
84
85
  }
85
86
  match tishlang_core::drain_iterator(&v) {
@@ -547,7 +548,8 @@ use tishlang_builtins::globals::{
547
548
  decode_uri as builtins_decode_uri, encode_uri as builtins_encode_uri,
548
549
  is_finite as builtins_is_finite, is_nan as builtins_is_nan,
549
550
  object_assign as builtins_object_assign, object_entries as builtins_object_entries,
550
- object_from_entries as builtins_object_from_entries, object_keys as builtins_object_keys,
551
+ object_freeze as builtins_object_freeze, object_from_entries as builtins_object_from_entries,
552
+ object_is_frozen as builtins_object_is_frozen, object_keys as builtins_object_keys,
551
553
  object_values as builtins_object_values,
552
554
  number_convert as builtins_number_convert,
553
555
  string_convert as builtins_string_convert,
@@ -1389,7 +1391,10 @@ pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
1389
1391
  if key == "length" {
1390
1392
  Value::Number(arr.borrow().len() as f64)
1391
1393
  } else if let Ok(idx) = key.parse::<usize>() {
1392
- arr.borrow().get(idx).copied().map(Value::Number).unwrap_or(Value::Null)
1394
+ arr.borrow().get(idx).map(|v| match v {
1395
+ Value::Number(n) if n.is_nan() => Value::Null,
1396
+ other => other,
1397
+ }).unwrap_or(Value::Null)
1393
1398
  } else {
1394
1399
  Value::Null
1395
1400
  }
@@ -1487,7 +1492,10 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
1487
1492
  },
1488
1493
  _ => return Value::Null,
1489
1494
  };
1490
- arr.borrow().get(idx).copied().map(Value::Number).unwrap_or(Value::Null)
1495
+ arr.borrow().get(idx).map(|v| match v {
1496
+ Value::Number(n) if n.is_nan() => Value::Null,
1497
+ other => other,
1498
+ }).unwrap_or(Value::Null)
1491
1499
  }
1492
1500
  // `str[i]` returns the character at index `i` (issue #17) — matches the VM /
1493
1501
  // interpreter; out-of-bounds / negative / non-integer indices yield null.
@@ -1558,6 +1566,14 @@ pub fn set_prop(obj: &Value, key: &str, val: Value) -> Value {
1558
1566
  // exists we re-use the existing `Arc<str>` and skip the
1559
1567
  // alloc. Only newly-inserted keys pay for `Arc::from(key)`.
1560
1568
  let mut m = map.borrow_mut();
1569
+ if m.frozen {
1570
+ // #437: a write to a frozen object throws a catchable TypeError (parked in the
1571
+ // pending-throw slot; the generated code surfaces it at the next checkpoint).
1572
+ tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
1573
+ "Cannot assign to read only property '{key}' of a frozen object"
1574
+ )));
1575
+ return val;
1576
+ }
1561
1577
  if let Some(slot) = m.strings.get_mut(key) {
1562
1578
  *slot = val.clone();
1563
1579
  } else {
@@ -1613,14 +1629,35 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
1613
1629
  },
1614
1630
  _ => panic!("Array index must be number"),
1615
1631
  };
1616
- let mut arr_mut = arr.borrow_mut();
1617
- while arr_mut.len() <= index {
1618
- arr_mut.push(0.0);
1632
+ let mut b = arr.borrow_mut();
1633
+ match (b.as_packed_mut(), val.as_number()) {
1634
+ (Some(nums), Some(n)) => {
1635
+ while nums.len() <= index {
1636
+ nums.push(f64::NAN);
1637
+ }
1638
+ nums[index] = n;
1639
+ }
1640
+ // Non-numeric (or already deopted): upgrade in place, store the real Value.
1641
+ _ => {
1642
+ let boxed = b.deopt();
1643
+ while boxed.len() <= index {
1644
+ boxed.push(Value::Null);
1645
+ }
1646
+ boxed[index] = val.clone();
1647
+ }
1619
1648
  }
1620
- arr_mut[index] = val.as_number().unwrap_or(f64::NAN);
1621
1649
  val
1622
1650
  }
1623
- Value::Object(_) => {
1651
+ Value::Object(map) => {
1652
+ if map.borrow().frozen {
1653
+ // #437: a write to a frozen object throws a catchable TypeError (parked; surfaced at
1654
+ // the next assignment checkpoint, same as `set_prop`).
1655
+ tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
1656
+ "Cannot assign to read only property '{}' of a frozen object",
1657
+ idx.to_display_string()
1658
+ )));
1659
+ return val;
1660
+ }
1624
1661
  tishlang_core::object_set(obj, idx, val.clone()).expect("object set");
1625
1662
  val
1626
1663
  }
@@ -1673,6 +1710,14 @@ pub fn object_keys(args: &[Value]) -> Value {
1673
1710
  builtins_object_keys(args)
1674
1711
  }
1675
1712
 
1713
+ pub fn object_freeze(args: &[Value]) -> Value {
1714
+ builtins_object_freeze(args)
1715
+ }
1716
+
1717
+ pub fn object_is_frozen(args: &[Value]) -> Value {
1718
+ builtins_object_is_frozen(args)
1719
+ }
1720
+
1676
1721
  pub fn object_values(args: &[Value]) -> Value {
1677
1722
  builtins_object_values(args)
1678
1723
  }
@@ -1735,7 +1780,7 @@ pub use pty::{pty_kill, pty_pid, pty_read, pty_resize, pty_spawn, pty_write};
1735
1780
  #[cfg(feature = "ws")]
1736
1781
  pub use ws::{
1737
1782
  web_socket_client, web_socket_server_accept, web_socket_server_construct,
1738
- web_socket_server_listen, ws_broadcast_native, ws_send_native,
1783
+ web_socket_server_listen, ws_broadcast_native, ws_send_native, ws_serve_accept,
1739
1784
  };
1740
1785
 
1741
1786
  #[cfg(feature = "http")]
@@ -82,6 +82,83 @@ fn unregister(id: u32) {
82
82
  CONNS.lock().unwrap().remove(&id);
83
83
  }
84
84
 
85
+ /// Bridge an already-handshaked WebSocket stream into the `CONNS` registry — spawning the read and
86
+ /// write pump tasks — and return its connection id. Shared by the `tish:ws` server-accept loop and
87
+ /// the `serve()` HTTP→WS upgrade path (see `http_hyper.rs`). Must be called inside a tokio runtime.
88
+ pub(crate) fn register_ws_stream<S>(ws_stream: tokio_tungstenite::WebSocketStream<S>) -> u32
89
+ where
90
+ S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
91
+ {
92
+ let (send_tx, mut send_rx) = tokio_mpsc::unbounded_channel::<String>();
93
+ let (recv_tx, recv_rx) = mpsc::sync_channel::<String>(64);
94
+ let id = register(send_tx, recv_rx);
95
+ let recv_tx_task = Arc::new(recv_tx);
96
+ let (mut write, mut read) = ws_stream.split();
97
+ tokio::spawn(async move {
98
+ while let Some(Ok(msg)) = read.next().await {
99
+ if let tokio_tungstenite::tungstenite::Message::Text(t) = msg {
100
+ let _ = recv_tx_task.send(t.to_string());
101
+ }
102
+ }
103
+ unregister(id);
104
+ });
105
+ tokio::spawn(async move {
106
+ while let Some(text) = send_rx.recv().await {
107
+ let _ = write
108
+ .send(tokio_tungstenite::tungstenite::Message::Text(text.into()))
109
+ .await;
110
+ }
111
+ });
112
+ id
113
+ }
114
+
115
+ lazy_static! {
116
+ /// Connections upgraded from HTTP by `serve()` (http_hyper), waiting for the program to pick them
117
+ /// up via `wsAccept`. Per-process: an upgraded socket stays in whatever SO_REUSEPORT worker
118
+ /// accepted it, so each worker drains its own queue and there's no cross-process routing problem.
119
+ static ref UPGRADE_QUEUE: (Mutex<std::collections::VecDeque<u32>>, std::sync::Condvar) =
120
+ (Mutex::new(std::collections::VecDeque::new()), std::sync::Condvar::new());
121
+ }
122
+
123
+ /// Enqueue a `serve()`-upgraded connection id for `wsAccept` to hand to the program.
124
+ pub(crate) fn enqueue_upgraded_conn(id: u32) {
125
+ let (m, cv) = &*UPGRADE_QUEUE;
126
+ if let Ok(mut q) = m.lock() {
127
+ q.push_back(id);
128
+ }
129
+ cv.notify_all();
130
+ }
131
+
132
+ /// `wsAccept(timeoutMs?)` — return the next connection upgraded on the HTTP server's port (see the
133
+ /// `serve()` HTTP→WS upgrade path) as a normal `tish:ws` connection object, or `Null` if none arrives
134
+ /// within the timeout (default 0 = non-blocking).
135
+ pub fn ws_serve_accept(args: &[Value]) -> Value {
136
+ let timeout_ms = match args.first() {
137
+ Some(Value::Number(n)) if n.is_finite() && *n >= 0.0 => (*n as u64).min(3_600_000),
138
+ _ => 0,
139
+ };
140
+ let (m, cv) = &*UPGRADE_QUEUE;
141
+ let mut q = match m.lock() {
142
+ Ok(q) => q,
143
+ Err(_) => return Value::Null,
144
+ };
145
+ if q.is_empty() && timeout_ms > 0 {
146
+ let res = cv.wait_timeout_while(
147
+ q,
148
+ std::time::Duration::from_millis(timeout_ms),
149
+ |q| q.is_empty(),
150
+ );
151
+ q = match res {
152
+ Ok((g, _)) => g,
153
+ Err(e) => e.into_inner().0,
154
+ };
155
+ }
156
+ match q.pop_front() {
157
+ Some(id) => conn_object(id),
158
+ None => Value::Null,
159
+ }
160
+ }
161
+
85
162
  /// Max simultaneous WebSocket connections. Each accepted conn registers in `CONNS` and
86
163
  /// spawns tasks + channels, freed only on close — unbounded accepts exhaust tasks/memory.
87
164
  /// Override with `TISH_WS_MAX_CONNS`; default 10000.
@@ -399,27 +476,7 @@ pub fn web_socket_server_listen(args: &[Value]) -> Value {
399
476
  continue;
400
477
  }
401
478
  };
402
- let (send_tx, mut send_rx) = tokio_mpsc::unbounded_channel::<String>();
403
- let (recv_tx, recv_rx) = mpsc::sync_channel::<String>(64);
404
- let id = register(send_tx, recv_rx);
405
- let recv_tx = Arc::new(recv_tx);
406
- let recv_tx_task = Arc::clone(&recv_tx);
407
- let (mut write, mut read) = ws_stream.split();
408
- tokio::spawn(async move {
409
- while let Some(Ok(msg)) = read.next().await {
410
- if let tokio_tungstenite::tungstenite::Message::Text(t) = msg {
411
- let _ = recv_tx_task.send(t.to_string());
412
- }
413
- }
414
- unregister(id);
415
- });
416
- tokio::spawn(async move {
417
- while let Some(text) = send_rx.recv().await {
418
- let _ = write
419
- .send(tokio_tungstenite::tungstenite::Message::Text(text.into()))
420
- .await;
421
- }
422
- });
479
+ let id = register_ws_stream(ws_stream);
423
480
  if conn_tx.send(id).is_err() {
424
481
  break;
425
482
  }
@@ -578,6 +635,22 @@ mod tests {
578
635
  use std::thread;
579
636
  use std::time::Duration;
580
637
 
638
+ /// The serve() HTTP→WS upgrade queue: `wsAccept` hands out enqueued conns FIFO and returns Null
639
+ /// when empty. (End-to-end upgrade over a live hyper port is covered by the native smoke test.)
640
+ #[test]
641
+ fn upgrade_queue_drains_then_empty() {
642
+ assert!(matches!(ws_serve_accept(&[Value::Number(0.0)]), Value::Null));
643
+ enqueue_upgraded_conn(4242);
644
+ match ws_serve_accept(&[Value::Number(0.0)]) {
645
+ Value::Object(o) => {
646
+ let b = o.borrow();
647
+ assert!(matches!(b.strings.get("_id"), Some(Value::Number(n)) if *n == 4242.0));
648
+ }
649
+ other => panic!("expected a conn object, got {:?}", other),
650
+ }
651
+ assert!(matches!(ws_serve_accept(&[Value::Number(0.0)]), Value::Null));
652
+ }
653
+
581
654
  #[test]
582
655
  fn ws_echo_roundtrip() {
583
656
  let port: u16 = 18_742;