@tishlang/tish-format 1.0.12 → 1.0.13

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 (164) hide show
  1. package/Cargo.toml +49 -0
  2. package/LICENSE +13 -0
  3. package/README.md +138 -0
  4. package/bin/tish-format +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +610 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +54 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +565 -0
  16. package/crates/tish/src/main.rs +781 -0
  17. package/crates/tish/src/repl_completion.rs +200 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  24. package/crates/tish/tests/integration_test.rs +1095 -0
  25. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  26. package/crates/tish/tests/shortcircuit.rs +65 -0
  27. package/crates/tish_ast/Cargo.toml +9 -0
  28. package/crates/tish_ast/src/ast.rs +620 -0
  29. package/crates/tish_ast/src/lib.rs +5 -0
  30. package/crates/tish_build_utils/Cargo.toml +11 -0
  31. package/crates/tish_build_utils/src/lib.rs +577 -0
  32. package/crates/tish_builtins/Cargo.toml +20 -0
  33. package/crates/tish_builtins/src/array.rs +441 -0
  34. package/crates/tish_builtins/src/construct.rs +159 -0
  35. package/crates/tish_builtins/src/globals.rs +213 -0
  36. package/crates/tish_builtins/src/helpers.rs +35 -0
  37. package/crates/tish_builtins/src/lib.rs +16 -0
  38. package/crates/tish_builtins/src/math.rs +89 -0
  39. package/crates/tish_builtins/src/object.rs +36 -0
  40. package/crates/tish_builtins/src/string.rs +647 -0
  41. package/crates/tish_builtins/src/symbol.rs +83 -0
  42. package/crates/tish_bytecode/Cargo.toml +17 -0
  43. package/crates/tish_bytecode/src/chunk.rs +96 -0
  44. package/crates/tish_bytecode/src/compiler.rs +1760 -0
  45. package/crates/tish_bytecode/src/encoding.rs +100 -0
  46. package/crates/tish_bytecode/src/lib.rs +19 -0
  47. package/crates/tish_bytecode/src/opcode.rs +142 -0
  48. package/crates/tish_bytecode/src/peephole.rs +189 -0
  49. package/crates/tish_bytecode/src/serialize.rs +163 -0
  50. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  51. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  52. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  53. package/crates/tish_compile/Cargo.toml +26 -0
  54. package/crates/tish_compile/src/codegen.rs +5332 -0
  55. package/crates/tish_compile/src/infer.rs +292 -0
  56. package/crates/tish_compile/src/lib.rs +164 -0
  57. package/crates/tish_compile/src/resolve.rs +1388 -0
  58. package/crates/tish_compile/src/types.rs +501 -0
  59. package/crates/tish_compile_js/Cargo.toml +18 -0
  60. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  61. package/crates/tish_compile_js/src/codegen.rs +871 -0
  62. package/crates/tish_compile_js/src/error.rs +20 -0
  63. package/crates/tish_compile_js/src/lib.rs +26 -0
  64. package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
  65. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  66. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  67. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  68. package/crates/tish_core/Cargo.toml +26 -0
  69. package/crates/tish_core/src/console_style.rs +160 -0
  70. package/crates/tish_core/src/json.rs +387 -0
  71. package/crates/tish_core/src/lib.rs +17 -0
  72. package/crates/tish_core/src/macros.rs +36 -0
  73. package/crates/tish_core/src/uri.rs +118 -0
  74. package/crates/tish_core/src/value.rs +696 -0
  75. package/crates/tish_core/src/vmref.rs +178 -0
  76. package/crates/tish_cranelift/Cargo.toml +19 -0
  77. package/crates/tish_cranelift/src/lib.rs +43 -0
  78. package/crates/tish_cranelift/src/link.rs +117 -0
  79. package/crates/tish_cranelift/src/lower.rs +85 -0
  80. package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
  81. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  82. package/crates/tish_eval/Cargo.toml +45 -0
  83. package/crates/tish_eval/src/eval.rs +3717 -0
  84. package/crates/tish_eval/src/http.rs +188 -0
  85. package/crates/tish_eval/src/lib.rs +99 -0
  86. package/crates/tish_eval/src/natives.rs +399 -0
  87. package/crates/tish_eval/src/promise.rs +179 -0
  88. package/crates/tish_eval/src/regex.rs +299 -0
  89. package/crates/tish_eval/src/timers.rs +120 -0
  90. package/crates/tish_eval/src/value.rs +318 -0
  91. package/crates/tish_eval/src/value_convert.rs +111 -0
  92. package/crates/tish_fmt/Cargo.toml +16 -0
  93. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  94. package/crates/tish_fmt/src/lib.rs +2101 -0
  95. package/crates/tish_jsx_web/Cargo.toml +9 -0
  96. package/crates/tish_jsx_web/README.md +5 -0
  97. package/crates/tish_jsx_web/src/lib.rs +2 -0
  98. package/crates/tish_lexer/Cargo.toml +9 -0
  99. package/crates/tish_lexer/src/lib.rs +716 -0
  100. package/crates/tish_lexer/src/token.rs +163 -0
  101. package/crates/tish_lint/Cargo.toml +18 -0
  102. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  103. package/crates/tish_lint/src/lib.rs +289 -0
  104. package/crates/tish_llvm/Cargo.toml +13 -0
  105. package/crates/tish_llvm/src/lib.rs +115 -0
  106. package/crates/tish_lsp/Cargo.toml +25 -0
  107. package/crates/tish_lsp/README.md +26 -0
  108. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  109. package/crates/tish_lsp/src/import_goto.rs +562 -0
  110. package/crates/tish_lsp/src/main.rs +1046 -0
  111. package/crates/tish_native/Cargo.toml +16 -0
  112. package/crates/tish_native/src/build.rs +427 -0
  113. package/crates/tish_native/src/config.rs +48 -0
  114. package/crates/tish_native/src/lib.rs +416 -0
  115. package/crates/tish_opt/Cargo.toml +13 -0
  116. package/crates/tish_opt/src/lib.rs +943 -0
  117. package/crates/tish_parser/Cargo.toml +11 -0
  118. package/crates/tish_parser/src/lib.rs +332 -0
  119. package/crates/tish_parser/src/parser.rs +2304 -0
  120. package/crates/tish_pg/Cargo.toml +34 -0
  121. package/crates/tish_pg/README.md +38 -0
  122. package/crates/tish_pg/src/error.rs +52 -0
  123. package/crates/tish_pg/src/lib.rs +955 -0
  124. package/crates/tish_resolve/Cargo.toml +13 -0
  125. package/crates/tish_resolve/src/lib.rs +3561 -0
  126. package/crates/tish_resolve/src/pos.rs +141 -0
  127. package/crates/tish_runtime/Cargo.toml +96 -0
  128. package/crates/tish_runtime/src/http.rs +1298 -0
  129. package/crates/tish_runtime/src/http_fetch.rs +471 -0
  130. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  131. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  132. package/crates/tish_runtime/src/lib.rs +1192 -0
  133. package/crates/tish_runtime/src/native_promise.rs +15 -0
  134. package/crates/tish_runtime/src/promise.rs +248 -0
  135. package/crates/tish_runtime/src/promise_io.rs +38 -0
  136. package/crates/tish_runtime/src/timers.rs +166 -0
  137. package/crates/tish_runtime/src/ws.rs +761 -0
  138. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  139. package/crates/tish_ui/Cargo.toml +17 -0
  140. package/crates/tish_ui/src/jsx.rs +682 -0
  141. package/crates/tish_ui/src/lib.rs +20 -0
  142. package/crates/tish_ui/src/runtime/hooks.rs +569 -0
  143. package/crates/tish_ui/src/runtime/mod.rs +180 -0
  144. package/crates/tish_vm/Cargo.toml +47 -0
  145. package/crates/tish_vm/src/lib.rs +39 -0
  146. package/crates/tish_vm/src/vm.rs +2192 -0
  147. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  148. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  149. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  150. package/crates/tish_wasm/Cargo.toml +15 -0
  151. package/crates/tish_wasm/src/lib.rs +424 -0
  152. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  153. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  154. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  155. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  156. package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
  157. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  158. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  159. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  160. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  161. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  162. package/justfile +268 -0
  163. package/package.json +1 -1
  164. package/platform/darwin-arm64/tish-fmt +0 -0
@@ -0,0 +1,15 @@
1
+ //! Native Promise entrypoints for fetch / fetchAll.
2
+
3
+ use tishlang_core::Value;
4
+
5
+ pub fn fetch_promise(args: Vec<Value>) -> Value {
6
+ crate::http_fetch::fetch_promise_from_args(args)
7
+ }
8
+
9
+ pub fn fetch_all_promise(args: Vec<Value>) -> Value {
10
+ crate::http_fetch::fetch_all_promise_from_args(args)
11
+ }
12
+
13
+ pub fn fetch_async_promise(args: Vec<Value>) -> Value {
14
+ fetch_promise(args)
15
+ }
@@ -0,0 +1,248 @@
1
+ //! Promise helpers for the bytecode VM and native codegen (`Promise.resolve`, etc.).
2
+ //!
3
+ //! The global `Promise` value is an **object** with a `__call` entry so the VM can
4
+ //! invoke `Promise(executor)` like `new Promise(executor)` in JS. Static methods live
5
+ //! on the same object (`resolve`, `reject`, `all`, `race`).
6
+
7
+ use std::sync::mpsc;
8
+ use std::sync::{Arc, Mutex};
9
+
10
+ use tishlang_core::{ObjectMap, TishPromise, Value, VmRef};
11
+
12
+ /// Fulfilled or rejected before anyone awaits — `block_until_settled` consumes the result once.
13
+ pub struct ImmediateSettledPromise {
14
+ slot: Mutex<Option<Result<Value, Value>>>,
15
+ }
16
+
17
+ impl ImmediateSettledPromise {
18
+ fn new(result: Result<Value, Value>) -> Self {
19
+ Self {
20
+ slot: Mutex::new(Some(result)),
21
+ }
22
+ }
23
+ }
24
+
25
+ impl TishPromise for ImmediateSettledPromise {
26
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
27
+ self.slot
28
+ .lock()
29
+ .unwrap()
30
+ .take()
31
+ .unwrap_or(Err(Value::String(
32
+ "Promise already settled or consumed".into(),
33
+ )))
34
+ }
35
+ }
36
+
37
+ fn fulfilled(v: Value) -> Value {
38
+ Value::Promise(Arc::new(ImmediateSettledPromise::new(Ok(v))))
39
+ }
40
+
41
+ fn rejected(v: Value) -> Value {
42
+ Value::Promise(Arc::new(ImmediateSettledPromise::new(Err(v))))
43
+ }
44
+
45
+ fn flatten_chain_out(v: Value) -> std::result::Result<Value, Value> {
46
+ match v {
47
+ Value::Promise(p) => p.block_until_settled(),
48
+ other => Ok(other),
49
+ }
50
+ }
51
+
52
+ /// `Promise(executor)` — executor runs synchronously; `resolve` / `reject` unblock `recv`.
53
+ struct DeferredChannelPromise {
54
+ rx: Mutex<Option<mpsc::Receiver<Result<Value, Value>>>>,
55
+ }
56
+
57
+ impl TishPromise for DeferredChannelPromise {
58
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
59
+ let rx = self.rx.lock().unwrap().take();
60
+ match rx {
61
+ Some(r) => r.recv().unwrap_or(Err(Value::String(
62
+ "Promise executor did not call resolve or reject".into(),
63
+ ))),
64
+ None => Err(Value::String(
65
+ "Promise already consumed or settled".into(),
66
+ )),
67
+ }
68
+ }
69
+ }
70
+
71
+ /// `.then` / `.catch` chain: when awaited, settle the predecessor then optionally invoke a handler.
72
+ pub struct ThenPromise {
73
+ pred: Arc<dyn TishPromise>,
74
+ on_fulfilled: Option<Value>,
75
+ on_rejected: Option<Value>,
76
+ }
77
+
78
+ impl TishPromise for ThenPromise {
79
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
80
+ match self.pred.block_until_settled() {
81
+ Ok(v) => {
82
+ if let Some(Value::Function(f)) = &self.on_fulfilled {
83
+ flatten_chain_out(f(&[v]))
84
+ } else {
85
+ Ok(v)
86
+ }
87
+ }
88
+ Err(e) => {
89
+ if let Some(Value::Function(f)) = &self.on_rejected {
90
+ flatten_chain_out(f(&[e]))
91
+ } else {
92
+ Err(e)
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ /// `Promise.resolve(value)` — adopt promises, otherwise wrap in a fulfilled promise.
100
+ pub fn promise_resolve(args: &[Value]) -> Value {
101
+ match args.first() {
102
+ Some(Value::Promise(p)) => Value::Promise(Arc::clone(p)),
103
+ Some(v) => fulfilled(v.clone()),
104
+ None => fulfilled(Value::Null),
105
+ }
106
+ }
107
+
108
+ /// `Promise.reject(reason)` — always a rejected promise.
109
+ pub fn promise_reject(args: &[Value]) -> Value {
110
+ rejected(
111
+ args.first()
112
+ .cloned()
113
+ .unwrap_or(Value::Null),
114
+ )
115
+ }
116
+
117
+ /// `Promise.all(iterable)` — block on each promise in order; non-promises pass through.
118
+ pub fn promise_all(args: &[Value]) -> Value {
119
+ match args.first() {
120
+ Some(Value::Array(arr)) => {
121
+ let mut out: Vec<Value> = Vec::new();
122
+ for v in arr.borrow().iter() {
123
+ let item = if let Value::Promise(p) = v {
124
+ match p.block_until_settled() {
125
+ Ok(x) => x,
126
+ Err(rej) => return rejected(rej),
127
+ }
128
+ } else {
129
+ v.clone()
130
+ };
131
+ out.push(item);
132
+ }
133
+ fulfilled(Value::Array(VmRef::new(out)))
134
+ }
135
+ Some(v) => fulfilled(v.clone()),
136
+ None => fulfilled(Value::Null),
137
+ }
138
+ }
139
+
140
+ /// `Promise.race(iterable)` — first element wins (blocking first promise if it is one).
141
+ pub fn promise_race(args: &[Value]) -> Value {
142
+ match args.first() {
143
+ Some(Value::Array(arr)) => {
144
+ let borrowed = arr.borrow();
145
+ for v in borrowed.iter() {
146
+ if let Value::Promise(p) = v {
147
+ return match p.block_until_settled() {
148
+ Ok(x) => fulfilled(x),
149
+ Err(e) => rejected(e),
150
+ };
151
+ }
152
+ return fulfilled(v.clone());
153
+ }
154
+ Value::Null
155
+ }
156
+ Some(v) => fulfilled(v.clone()),
157
+ None => Value::Null,
158
+ }
159
+ }
160
+
161
+ /// Build the global `Promise` object: `__call` (constructor) + static methods.
162
+ pub fn promise_object() -> Value {
163
+ let mut map: ObjectMap = ObjectMap::default();
164
+
165
+ let ctor = Value::native(|args: &[Value]| match args.first() {
166
+ Some(Value::Function(f)) => {
167
+ let (tx, rx) = mpsc::channel();
168
+ let tx_cell = Arc::new(Mutex::new(Some(tx)));
169
+ let resolve = Value::native({
170
+ let tx_cell = Arc::clone(&tx_cell);
171
+ move |a: &[Value]| {
172
+ if let Some(t) = tx_cell.lock().unwrap().take() {
173
+ let _ = t.send(Ok(
174
+ a.first().cloned().unwrap_or(Value::Null),
175
+ ));
176
+ }
177
+ Value::Null
178
+ }
179
+ });
180
+ let reject = Value::native({
181
+ let tx_cell = Arc::clone(&tx_cell);
182
+ move |a: &[Value]| {
183
+ if let Some(t) = tx_cell.lock().unwrap().take() {
184
+ let _ = t.send(Err(
185
+ a.first().cloned().unwrap_or(Value::Null),
186
+ ));
187
+ }
188
+ Value::Null
189
+ }
190
+ });
191
+ let _ = f(&[resolve, reject]);
192
+ Value::Promise(Arc::new(DeferredChannelPromise {
193
+ rx: Mutex::new(Some(rx)),
194
+ }))
195
+ }
196
+ _ => Value::Null,
197
+ });
198
+
199
+ map.insert(Arc::from("__call"), ctor);
200
+ map.insert(
201
+ Arc::from("resolve"),
202
+ Value::native(|args: &[Value]| promise_resolve(args)),
203
+ );
204
+ map.insert(
205
+ Arc::from("reject"),
206
+ Value::native(|args: &[Value]| promise_reject(args)),
207
+ );
208
+ map.insert(
209
+ Arc::from("all"),
210
+ Value::native(|args: &[Value]| promise_all(args)),
211
+ );
212
+ map.insert(
213
+ Arc::from("race"),
214
+ Value::native(|args: &[Value]| promise_race(args)),
215
+ );
216
+ Value::object(map)
217
+ }
218
+
219
+ /// `.then(onFulfilled, onRejected)` for a `Value::Promise` instance (VM `GetMember`).
220
+ pub fn promise_instance_then(p: &Arc<dyn TishPromise>, args: &[Value]) -> Value {
221
+ Value::Promise(Arc::new(ThenPromise {
222
+ pred: Arc::clone(p),
223
+ on_fulfilled: args.first().cloned(),
224
+ on_rejected: args.get(1).cloned(),
225
+ }))
226
+ }
227
+
228
+ /// `.catch(onRejected)` for a `Value::Promise` instance.
229
+ pub fn promise_instance_catch(p: &Arc<dyn TishPromise>, args: &[Value]) -> Value {
230
+ Value::Promise(Arc::new(ThenPromise {
231
+ pred: Arc::clone(p),
232
+ on_fulfilled: None,
233
+ on_rejected: args.first().cloned(),
234
+ }))
235
+ }
236
+
237
+ /// Unwrap a settled [`Value::Promise`], or pass non-promise values through (VM `AwaitPromise` /
238
+ /// `tish:http.await`). Fetch promises still require the `http` feature.
239
+ pub fn await_promise(v: Value) -> Value {
240
+ if let Value::Promise(p) = v {
241
+ match p.block_until_settled() {
242
+ Ok(val) => val,
243
+ Err(rejection) => rejection,
244
+ }
245
+ } else {
246
+ v
247
+ }
248
+ }
@@ -0,0 +1,38 @@
1
+ //! Promises carrying only Send payloads (string results for text(), etc.).
2
+
3
+ use std::sync::{Arc, Mutex};
4
+ use tishlang_core::{ObjectMap, TishPromise, Value};
5
+ use tokio::sync::oneshot;
6
+
7
+ fn error_value(msg: String) -> Value {
8
+ let mut obj: ObjectMap = ObjectMap::with_capacity(2);
9
+ obj.insert(Arc::from("error"), Value::String(msg.into()));
10
+ obj.insert(Arc::from("ok"), Value::Bool(false));
11
+ Value::object(obj)
12
+ }
13
+
14
+ pub struct StringResultPromise {
15
+ pub(crate) rx: Mutex<Option<oneshot::Receiver<Result<String, String>>>>,
16
+ }
17
+
18
+ impl TishPromise for StringResultPromise {
19
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
20
+ let rx = self.rx.lock().unwrap().take();
21
+ if let Some(rx) = rx {
22
+ let result = crate::http::block_on_http(rx);
23
+ match result {
24
+ Ok(Ok(s)) => Ok(Value::String(s.into())),
25
+ Ok(Err(e)) => Err(error_value(e)),
26
+ Err(_) => Err(Value::String("Promise dropped".into())),
27
+ }
28
+ } else {
29
+ Err(Value::String("Promise already consumed".into()))
30
+ }
31
+ }
32
+ }
33
+
34
+ pub fn string_result_promise(rx: oneshot::Receiver<Result<String, String>>) -> Value {
35
+ Value::Promise(Arc::new(StringResultPromise {
36
+ rx: Mutex::new(Some(rx)),
37
+ }))
38
+ }
@@ -0,0 +1,166 @@
1
+ //! setTimeout, setInterval, clearTimeout, clearInterval for compiled Tish and VM.
2
+ //! Callbacks run when blocking ops (e.g. ws.receiveTimeout) yield in their poll loop.
3
+
4
+ use std::cell::RefCell;
5
+ use std::collections::HashMap;
6
+ use std::sync::atomic::{AtomicU64, Ordering};
7
+ use std::time::{Duration, Instant};
8
+
9
+ use tishlang_core::Value;
10
+
11
+ static NEXT_ID: AtomicU64 = AtomicU64::new(1);
12
+
13
+ fn next_id() -> u64 {
14
+ NEXT_ID.fetch_add(1, Ordering::SeqCst)
15
+ }
16
+
17
+ struct TimerEntry {
18
+ due: Instant,
19
+ callback: Value,
20
+ args: Vec<Value>,
21
+ interval_ms: u64,
22
+ }
23
+
24
+ thread_local! {
25
+ static REGISTRY: RefCell<HashMap<u64, TimerEntry>> = RefCell::new(HashMap::new());
26
+ }
27
+
28
+ fn extract_num(v: Option<&Value>) -> u64 {
29
+ v.and_then(|x| match x {
30
+ Value::Number(n) if n.is_finite() && *n >= 0.0 => Some(*n as u64),
31
+ _ => None,
32
+ })
33
+ .unwrap_or(0)
34
+ }
35
+
36
+ /// Sleep for ms, running due timers before sleeping. Use this instead of thread::sleep
37
+ /// in blocking loops so setTimeout callbacks can fire.
38
+ #[allow(dead_code)] // Used by embedders with blocking poll loops; AppKit uses [`drain_timers`] instead.
39
+ pub fn sleep_with_drain(ms: u64) {
40
+ run_due_timers();
41
+ std::thread::sleep(Duration::from_millis(ms));
42
+ }
43
+
44
+ /// Run all due timer callbacks (e.g. from an AppKit / GUI event pump).
45
+ #[inline]
46
+ pub fn drain_timers() {
47
+ run_due_timers();
48
+ }
49
+
50
+ /// Run all due timer callbacks (including timers scheduled by other timers).
51
+ fn run_due_timers() {
52
+ for _ in 0..64 {
53
+ let due = take_due_timers();
54
+ if due.is_empty() {
55
+ break;
56
+ }
57
+ for (id, callback, args, interval_ms) in due {
58
+ if let Value::Function(f) = &callback {
59
+ let _ = f(&args);
60
+ }
61
+ if interval_ms > 0 {
62
+ re_register_interval(id, callback, args, interval_ms);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ fn take_due_timers() -> Vec<(u64, Value, Vec<Value>, u64)> {
69
+ let now = Instant::now();
70
+ REGISTRY.with(|r| {
71
+ let mut reg = r.borrow_mut();
72
+ let due: Vec<_> = reg
73
+ .iter()
74
+ .filter(|(_, e)| e.due <= now)
75
+ .map(|(id, e)| (*id, e.callback.clone(), e.args.clone(), e.interval_ms))
76
+ .collect();
77
+ for (id, _, _, _) in &due {
78
+ reg.remove(id);
79
+ }
80
+ due
81
+ })
82
+ }
83
+
84
+ fn re_register_interval(id: u64, callback: Value, args: Vec<Value>, interval_ms: u64) {
85
+ let due = Instant::now() + Duration::from_millis(interval_ms);
86
+ REGISTRY.with(|r| {
87
+ r.borrow_mut().insert(
88
+ id,
89
+ TimerEntry {
90
+ due,
91
+ callback,
92
+ args,
93
+ interval_ms,
94
+ },
95
+ );
96
+ });
97
+ }
98
+
99
+ /// setTimeout(callback, delayMs, ...args) - returns timer id.
100
+ /// Callbacks run when run_due_timers() is invoked (e.g. from ws.receiveTimeout poll loop).
101
+ pub fn set_timeout(args: &[Value]) -> Value {
102
+ let callback = args.first().cloned().unwrap_or(Value::Null);
103
+ let delay_ms = extract_num(args.get(1)).min(3600_000);
104
+ let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
105
+ if matches!(callback, Value::Null) {
106
+ return Value::Number(next_id() as f64);
107
+ }
108
+ let id = next_id();
109
+ let due = Instant::now() + Duration::from_millis(delay_ms);
110
+ REGISTRY.with(|r| {
111
+ r.borrow_mut().insert(
112
+ id,
113
+ TimerEntry {
114
+ due,
115
+ callback,
116
+ args: extra_args,
117
+ interval_ms: 0,
118
+ },
119
+ );
120
+ });
121
+ Value::Number(id as f64)
122
+ }
123
+
124
+ /// setInterval(callback, intervalMs, ...args) — first run after `intervalMs`, then repeats.
125
+ pub fn set_interval(args: &[Value]) -> Value {
126
+ let callback = args.first().cloned().unwrap_or(Value::Null);
127
+ let interval_ms = extract_num(args.get(1)).min(3600_000);
128
+ let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
129
+ if matches!(callback, Value::Null) {
130
+ return Value::Number(next_id() as f64);
131
+ }
132
+ let id = next_id();
133
+ let due = Instant::now() + Duration::from_millis(interval_ms);
134
+ REGISTRY.with(|r| {
135
+ r.borrow_mut().insert(
136
+ id,
137
+ TimerEntry {
138
+ due,
139
+ callback,
140
+ args: extra_args,
141
+ interval_ms,
142
+ },
143
+ );
144
+ });
145
+ Value::Number(id as f64)
146
+ }
147
+
148
+ /// clearTimeout(id) - removes timer.
149
+ pub fn clear_timeout(args: &[Value]) -> Value {
150
+ let id = args
151
+ .first()
152
+ .and_then(|v| match v {
153
+ Value::Number(n) if n.is_finite() && *n >= 0.0 => Some(*n as u64),
154
+ _ => None,
155
+ })
156
+ .unwrap_or(0);
157
+ REGISTRY.with(|r| {
158
+ r.borrow_mut().remove(&id);
159
+ });
160
+ Value::Null
161
+ }
162
+
163
+ /// clearInterval(id) — same registry as clearTimeout.
164
+ pub fn clear_interval(args: &[Value]) -> Value {
165
+ clear_timeout(args)
166
+ }