@tishlang/tish 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -1,76 +1,235 @@
|
|
|
1
|
-
//! Promise
|
|
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`).
|
|
2
6
|
|
|
3
|
-
use std::
|
|
4
|
-
use std::
|
|
5
|
-
use std::sync::Arc;
|
|
6
|
-
use tishlang_core::{ObjectMap, Value};
|
|
7
|
+
use std::sync::mpsc;
|
|
8
|
+
use std::sync::{Arc, Mutex};
|
|
7
9
|
|
|
8
|
-
|
|
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.
|
|
9
100
|
pub fn promise_resolve(args: &[Value]) -> Value {
|
|
10
|
-
args.first()
|
|
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
|
+
}
|
|
11
106
|
}
|
|
12
107
|
|
|
13
|
-
/// Promise.reject(
|
|
14
|
-
/// Note: await on this in native compile may not throw; use try/catch in interpreter for full support.
|
|
108
|
+
/// `Promise.reject(reason)` — always a rejected promise.
|
|
15
109
|
pub fn promise_reject(args: &[Value]) -> Value {
|
|
16
|
-
|
|
110
|
+
rejected(
|
|
111
|
+
args.first()
|
|
112
|
+
.cloned()
|
|
113
|
+
.unwrap_or(Value::Null),
|
|
114
|
+
)
|
|
17
115
|
}
|
|
18
116
|
|
|
19
|
-
/// Promise.all(iterable)
|
|
117
|
+
/// `Promise.all(iterable)` — block on each promise in order; non-promises pass through.
|
|
20
118
|
pub fn promise_all(args: &[Value]) -> Value {
|
|
21
119
|
match args.first() {
|
|
22
120
|
Some(Value::Array(arr)) => {
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Ok(val) => val,
|
|
30
|
-
Err(rejection) => rejection,
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
v.clone()
|
|
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),
|
|
34
127
|
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
128
|
+
} else {
|
|
129
|
+
v.clone()
|
|
130
|
+
};
|
|
131
|
+
out.push(item);
|
|
132
|
+
}
|
|
133
|
+
fulfilled(Value::Array(VmRef::new(out)))
|
|
38
134
|
}
|
|
39
|
-
Some(v) => v.clone(),
|
|
40
|
-
None => Value::Null,
|
|
135
|
+
Some(v) => fulfilled(v.clone()),
|
|
136
|
+
None => fulfilled(Value::Null),
|
|
41
137
|
}
|
|
42
138
|
}
|
|
43
139
|
|
|
44
|
-
/// Promise.race(iterable)
|
|
140
|
+
/// `Promise.race(iterable)` — first element wins (blocking first promise if it is one).
|
|
45
141
|
pub fn promise_race(args: &[Value]) -> Value {
|
|
46
142
|
match args.first() {
|
|
47
|
-
Some(Value::Array(arr)) =>
|
|
48
|
-
.borrow()
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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,
|
|
53
158
|
}
|
|
54
159
|
}
|
|
55
160
|
|
|
56
|
-
/// Build the Promise object
|
|
161
|
+
/// Build the global `Promise` object: `__call` (constructor) + static methods.
|
|
57
162
|
pub fn promise_object() -> Value {
|
|
58
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);
|
|
59
200
|
map.insert(
|
|
60
201
|
Arc::from("resolve"),
|
|
61
|
-
Value::
|
|
202
|
+
Value::native(|args: &[Value]| promise_resolve(args)),
|
|
62
203
|
);
|
|
63
204
|
map.insert(
|
|
64
205
|
Arc::from("reject"),
|
|
65
|
-
Value::
|
|
206
|
+
Value::native(|args: &[Value]| promise_reject(args)),
|
|
66
207
|
);
|
|
67
208
|
map.insert(
|
|
68
209
|
Arc::from("all"),
|
|
69
|
-
Value::
|
|
210
|
+
Value::native(|args: &[Value]| promise_all(args)),
|
|
70
211
|
);
|
|
71
212
|
map.insert(
|
|
72
213
|
Arc::from("race"),
|
|
73
|
-
Value::
|
|
214
|
+
Value::native(|args: &[Value]| promise_race(args)),
|
|
74
215
|
);
|
|
75
|
-
Value::Object(
|
|
216
|
+
Value::Object(VmRef::new(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
|
+
}))
|
|
76
235
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! Promises carrying only Send payloads (string results for text(), etc.).
|
|
2
2
|
|
|
3
3
|
use std::cell::RefCell;
|
|
4
|
+
use tishlang_core::VmRef;
|
|
4
5
|
use std::rc::Rc;
|
|
5
6
|
use std::sync::{Arc, Mutex};
|
|
6
7
|
use tishlang_core::{ObjectMap, TishPromise, Value};
|
|
@@ -10,7 +11,7 @@ fn error_value(msg: String) -> Value {
|
|
|
10
11
|
let mut obj: ObjectMap = ObjectMap::with_capacity(2);
|
|
11
12
|
obj.insert(Arc::from("error"), Value::String(msg.into()));
|
|
12
13
|
obj.insert(Arc::from("ok"), Value::Bool(false));
|
|
13
|
-
Value::Object(
|
|
14
|
+
Value::Object(VmRef::new(obj))
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
pub struct StringResultPromise {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! setTimeout, clearTimeout for compiled Tish and VM.
|
|
1
|
+
//! setTimeout, setInterval, clearTimeout, clearInterval for compiled Tish and VM.
|
|
2
2
|
//! Callbacks run when blocking ops (e.g. ws.receiveTimeout) yield in their poll loop.
|
|
3
3
|
|
|
4
4
|
use std::cell::RefCell;
|
|
@@ -35,11 +35,18 @@ fn extract_num(v: Option<&Value>) -> u64 {
|
|
|
35
35
|
|
|
36
36
|
/// Sleep for ms, running due timers before sleeping. Use this instead of thread::sleep
|
|
37
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.
|
|
38
39
|
pub fn sleep_with_drain(ms: u64) {
|
|
39
40
|
run_due_timers();
|
|
40
41
|
std::thread::sleep(Duration::from_millis(ms));
|
|
41
42
|
}
|
|
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
|
+
|
|
43
50
|
/// Run all due timer callbacks.
|
|
44
51
|
fn run_due_timers() {
|
|
45
52
|
let due = take_due_timers();
|
|
@@ -109,6 +116,30 @@ pub fn set_timeout(args: &[Value]) -> Value {
|
|
|
109
116
|
Value::Number(id as f64)
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
/// setInterval(callback, intervalMs, ...args) — first run after `intervalMs`, then repeats.
|
|
120
|
+
pub fn set_interval(args: &[Value]) -> Value {
|
|
121
|
+
let callback = args.first().cloned().unwrap_or(Value::Null);
|
|
122
|
+
let interval_ms = extract_num(args.get(1)).min(3600_000);
|
|
123
|
+
let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
|
|
124
|
+
if matches!(callback, Value::Null) {
|
|
125
|
+
return Value::Number(next_id() as f64);
|
|
126
|
+
}
|
|
127
|
+
let id = next_id();
|
|
128
|
+
let due = Instant::now() + Duration::from_millis(interval_ms);
|
|
129
|
+
REGISTRY.with(|r| {
|
|
130
|
+
r.borrow_mut().insert(
|
|
131
|
+
id,
|
|
132
|
+
TimerEntry {
|
|
133
|
+
due,
|
|
134
|
+
callback,
|
|
135
|
+
args: extra_args,
|
|
136
|
+
interval_ms,
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
Value::Number(id as f64)
|
|
141
|
+
}
|
|
142
|
+
|
|
112
143
|
/// clearTimeout(id) - removes timer.
|
|
113
144
|
pub fn clear_timeout(args: &[Value]) -> Value {
|
|
114
145
|
let id = args
|
|
@@ -123,3 +154,8 @@ pub fn clear_timeout(args: &[Value]) -> Value {
|
|
|
123
154
|
});
|
|
124
155
|
Value::Null
|
|
125
156
|
}
|
|
157
|
+
|
|
158
|
+
/// clearInterval(id) — same registry as clearTimeout.
|
|
159
|
+
pub fn clear_interval(args: &[Value]) -> Value {
|
|
160
|
+
clear_timeout(args)
|
|
161
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
//! - **Broadcast** (Node pattern): `server.clients.forEach(ws => ws.send(data))` or iterate room conns and `wsSend(ws, data)` (same as `ws.send(data)`)
|
|
7
7
|
|
|
8
8
|
use std::cell::RefCell;
|
|
9
|
+
use tishlang_core::VmRef;
|
|
9
10
|
use std::collections::HashMap;
|
|
10
11
|
use std::rc::Rc;
|
|
11
12
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
@@ -16,8 +17,8 @@ use std::time::{Duration, Instant};
|
|
|
16
17
|
use futures_util::{SinkExt, StreamExt};
|
|
17
18
|
use lazy_static::lazy_static;
|
|
18
19
|
use tishlang_core::{ObjectMap, Value};
|
|
19
|
-
use tokio::sync::mpsc as tokio_mpsc;
|
|
20
20
|
use tokio::runtime::Runtime;
|
|
21
|
+
use tokio::sync::mpsc as tokio_mpsc;
|
|
21
22
|
|
|
22
23
|
thread_local! {
|
|
23
24
|
/// Multi-thread runtime so `tokio::spawn` I/O tasks keep running after `block_on` returns.
|
|
@@ -199,39 +200,42 @@ fn conn_object(id: u32) -> Value {
|
|
|
199
200
|
obj.insert(Arc::from("readyState"), Value::Number(1.0)); // OPEN
|
|
200
201
|
obj.insert(
|
|
201
202
|
Arc::from("send"),
|
|
202
|
-
Value::
|
|
203
|
-
let data = args
|
|
203
|
+
Value::native(move |args: &[Value]| {
|
|
204
|
+
let data = args
|
|
205
|
+
.first()
|
|
206
|
+
.map(|v| v.to_display_string())
|
|
207
|
+
.unwrap_or_default();
|
|
204
208
|
Value::Bool(conn_send(id, data))
|
|
205
|
-
})
|
|
209
|
+
}),
|
|
206
210
|
);
|
|
207
211
|
obj.insert(
|
|
208
212
|
Arc::from("close"),
|
|
209
|
-
Value::
|
|
213
|
+
Value::native(move |_args: &[Value]| {
|
|
210
214
|
unregister(id);
|
|
211
215
|
Value::Null
|
|
212
|
-
})
|
|
216
|
+
}),
|
|
213
217
|
);
|
|
214
218
|
obj.insert(
|
|
215
219
|
Arc::from("receive"),
|
|
216
|
-
Value::
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Value::Object(Rc::new(RefCell::new(ev)))
|
|
222
|
-
}
|
|
223
|
-
None => Value::Null,
|
|
220
|
+
Value::native(move |_args: &[Value]| match conn_receive(id) {
|
|
221
|
+
Some(s) => {
|
|
222
|
+
let mut ev: ObjectMap = ObjectMap::default();
|
|
223
|
+
ev.insert(Arc::from("data"), Value::String(s.into()));
|
|
224
|
+
Value::Object(VmRef::new(ev))
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
+
None => Value::Null,
|
|
227
|
+
}),
|
|
226
228
|
);
|
|
227
229
|
let id_timeout = id;
|
|
228
230
|
obj.insert(
|
|
229
231
|
Arc::from("receiveTimeout"),
|
|
230
|
-
Value::
|
|
232
|
+
Value::native(move |args: &[Value]| {
|
|
231
233
|
let timeout_ms = args
|
|
232
234
|
.first()
|
|
233
235
|
.and_then(|v| match v {
|
|
234
|
-
Value::Number(n) if n.is_finite() && *n >= 0.0 =>
|
|
236
|
+
Value::Number(n) if n.is_finite() && *n >= 0.0 => {
|
|
237
|
+
Some((*n as u64).min(3600_000))
|
|
238
|
+
}
|
|
235
239
|
_ => None,
|
|
236
240
|
})
|
|
237
241
|
.unwrap_or(1000);
|
|
@@ -239,13 +243,13 @@ fn conn_object(id: u32) -> Value {
|
|
|
239
243
|
Some(s) => {
|
|
240
244
|
let mut ev: ObjectMap = ObjectMap::default();
|
|
241
245
|
ev.insert(Arc::from("data"), Value::String(s.into()));
|
|
242
|
-
Value::Object(
|
|
246
|
+
Value::Object(VmRef::new(ev))
|
|
243
247
|
}
|
|
244
248
|
None => Value::Null,
|
|
245
249
|
}
|
|
246
|
-
})
|
|
250
|
+
}),
|
|
247
251
|
);
|
|
248
|
-
Value::Object(
|
|
252
|
+
Value::Object(VmRef::new(obj))
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
fn parse_port(args: &[Value]) -> Option<u16> {
|
|
@@ -366,11 +370,17 @@ pub fn web_socket_server_listen(args: &[Value]) -> Value {
|
|
|
366
370
|
};
|
|
367
371
|
let ws_stream = match tokio_tungstenite::accept_async(stream).await {
|
|
368
372
|
Ok(ws) => {
|
|
369
|
-
eprintln!(
|
|
373
|
+
eprintln!(
|
|
374
|
+
"[tish ws] server accepted connection (handshake OK): port {}",
|
|
375
|
+
port
|
|
376
|
+
);
|
|
370
377
|
ws
|
|
371
378
|
}
|
|
372
379
|
Err(e) => {
|
|
373
|
-
eprintln!(
|
|
380
|
+
eprintln!(
|
|
381
|
+
"[tish ws] server accept_async failed: {} (port {})",
|
|
382
|
+
e, port
|
|
383
|
+
);
|
|
374
384
|
continue;
|
|
375
385
|
}
|
|
376
386
|
};
|
|
@@ -463,9 +473,9 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
463
473
|
}
|
|
464
474
|
|
|
465
475
|
// Node.js-compatible: server.clients is array of connected WebSocket instances
|
|
466
|
-
let clients:
|
|
476
|
+
let clients: VmRef<Vec<Value>> = VmRef::new(Vec::new());
|
|
467
477
|
|
|
468
|
-
let on_fn =
|
|
478
|
+
let on_fn = Value::native(|args: &[Value]| {
|
|
469
479
|
let Some(Value::Object(so)) = args.first() else {
|
|
470
480
|
return Value::Null;
|
|
471
481
|
};
|
|
@@ -475,14 +485,13 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
475
485
|
.unwrap_or_default();
|
|
476
486
|
let cb = args.get(2).cloned().unwrap_or(Value::Null);
|
|
477
487
|
if event == "connection" {
|
|
478
|
-
so.borrow_mut()
|
|
479
|
-
.insert(Arc::from("_onConnection"), cb);
|
|
488
|
+
so.borrow_mut().insert(Arc::from("_onConnection"), cb);
|
|
480
489
|
}
|
|
481
490
|
Value::Null
|
|
482
491
|
});
|
|
483
492
|
|
|
484
|
-
let clients_listen =
|
|
485
|
-
let listen_fn =
|
|
493
|
+
let clients_listen = clients.clone();
|
|
494
|
+
let listen_fn = Value::native(move |args: &[Value]| {
|
|
486
495
|
let Some(Value::Object(so)) = args.first() else {
|
|
487
496
|
return Value::Null;
|
|
488
497
|
};
|
|
@@ -511,8 +520,8 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
511
520
|
Value::Null
|
|
512
521
|
});
|
|
513
522
|
|
|
514
|
-
let clients_accept =
|
|
515
|
-
let accept_timeout_fn =
|
|
523
|
+
let clients_accept = clients.clone();
|
|
524
|
+
let accept_timeout_fn = Value::native(move |args: &[Value]| {
|
|
516
525
|
let Some(Value::Object(so)) = args.first() else {
|
|
517
526
|
return Value::Null;
|
|
518
527
|
};
|
|
@@ -533,10 +542,10 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
533
542
|
m.insert(Arc::from("_handle"), handle_val);
|
|
534
543
|
m.insert(Arc::from("_onConnection"), Value::Null);
|
|
535
544
|
m.insert(Arc::from("clients"), Value::Array(clients));
|
|
536
|
-
m.insert(Arc::from("on"),
|
|
537
|
-
m.insert(Arc::from("listen"),
|
|
538
|
-
m.insert(Arc::from("acceptTimeout"),
|
|
539
|
-
Value::Object(
|
|
545
|
+
m.insert(Arc::from("on"), on_fn);
|
|
546
|
+
m.insert(Arc::from("listen"), listen_fn);
|
|
547
|
+
m.insert(Arc::from("acceptTimeout"), accept_timeout_fn);
|
|
548
|
+
Value::Object(VmRef::new(m))
|
|
540
549
|
}
|
|
541
550
|
|
|
542
551
|
#[cfg(test)]
|
|
@@ -551,7 +560,7 @@ mod tests {
|
|
|
551
560
|
let opts = {
|
|
552
561
|
let mut m: ObjectMap = ObjectMap::default();
|
|
553
562
|
m.insert(Arc::from("port"), Value::Number(port as f64));
|
|
554
|
-
Value::Object(
|
|
563
|
+
Value::Object(VmRef::new(m))
|
|
555
564
|
};
|
|
556
565
|
|
|
557
566
|
let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
|
|
@@ -578,7 +587,9 @@ mod tests {
|
|
|
578
587
|
.unwrap_or_default(),
|
|
579
588
|
_ => String::new(),
|
|
580
589
|
};
|
|
581
|
-
if let Some(Value::Function(sf)) =
|
|
590
|
+
if let Some(Value::Function(sf)) =
|
|
591
|
+
wso.borrow().get(&Arc::from("send")).cloned()
|
|
592
|
+
{
|
|
582
593
|
let _ = sf(&[Value::String(data.into())]);
|
|
583
594
|
}
|
|
584
595
|
break;
|
|
@@ -634,7 +645,7 @@ mod tests {
|
|
|
634
645
|
let opts = {
|
|
635
646
|
let mut m: ObjectMap = ObjectMap::default();
|
|
636
647
|
m.insert(Arc::from("port"), Value::Number(port as f64));
|
|
637
|
-
Value::Object(
|
|
648
|
+
Value::Object(VmRef::new(m))
|
|
638
649
|
};
|
|
639
650
|
|
|
640
651
|
let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
|
|
@@ -666,8 +677,8 @@ mod tests {
|
|
|
666
677
|
if data.contains("\"type\":\"join\"") || data.contains("\"type\": \"join\"") {
|
|
667
678
|
let joined = r#"{"type":"joined","sessionId":"default"}"#;
|
|
668
679
|
let presence = r#"{"type":"presence","agentLanes":["ai-a"]}"#;
|
|
669
|
-
ws_send_native(&Value::Object(
|
|
670
|
-
ws_send_native(&Value::Object(
|
|
680
|
+
ws_send_native(&Value::Object(wso.clone()), joined);
|
|
681
|
+
ws_send_native(&Value::Object(wso.clone()), presence);
|
|
671
682
|
return;
|
|
672
683
|
}
|
|
673
684
|
}
|
|
@@ -692,7 +703,11 @@ mod tests {
|
|
|
692
703
|
let _ = send_f(&[Value::String(join_msg.into())]);
|
|
693
704
|
|
|
694
705
|
// Client uses receiveTimeout like the agent
|
|
695
|
-
let recv_timeout = co
|
|
706
|
+
let recv_timeout = co
|
|
707
|
+
.borrow()
|
|
708
|
+
.get(&Arc::from("receiveTimeout"))
|
|
709
|
+
.cloned()
|
|
710
|
+
.unwrap();
|
|
696
711
|
let Value::Function(recv_timeout_f) = recv_timeout else {
|
|
697
712
|
panic!("no receiveTimeout");
|
|
698
713
|
};
|
|
@@ -707,7 +722,11 @@ mod tests {
|
|
|
707
722
|
.get(&Arc::from("data"))
|
|
708
723
|
.map(|v| v.to_display_string())
|
|
709
724
|
.unwrap_or_default();
|
|
710
|
-
assert!(
|
|
725
|
+
assert!(
|
|
726
|
+
data1.contains("\"type\":\"joined\""),
|
|
727
|
+
"expected joined, got {}",
|
|
728
|
+
data1
|
|
729
|
+
);
|
|
711
730
|
|
|
712
731
|
let got2 = recv_timeout_f(&[timeout_arg]);
|
|
713
732
|
let Value::Object(ev2) = got2 else {
|
|
@@ -718,7 +737,11 @@ mod tests {
|
|
|
718
737
|
.get(&Arc::from("data"))
|
|
719
738
|
.map(|v| v.to_display_string())
|
|
720
739
|
.unwrap_or_default();
|
|
721
|
-
assert!(
|
|
740
|
+
assert!(
|
|
741
|
+
data2.contains("\"type\":\"presence\""),
|
|
742
|
+
"expected presence, got {}",
|
|
743
|
+
data2
|
|
744
|
+
);
|
|
722
745
|
|
|
723
746
|
let _ = server.join();
|
|
724
747
|
}
|
|
@@ -51,16 +51,17 @@ fn fetch_readable_stream_read_chunks() {
|
|
|
51
51
|
Value::Object(o) => o.borrow(),
|
|
52
52
|
_ => panic!("expected object response, got {:?}", resp),
|
|
53
53
|
};
|
|
54
|
-
assert!(obj
|
|
54
|
+
assert!(obj
|
|
55
|
+
.get(&std::sync::Arc::from("ok"))
|
|
56
|
+
.map(|v| matches!(v, Value::Bool(true)))
|
|
57
|
+
.unwrap_or(false));
|
|
55
58
|
|
|
56
59
|
let body = obj.get(&std::sync::Arc::from("body")).expect("body");
|
|
57
60
|
let stream = match body {
|
|
58
61
|
Value::Opaque(s) => s.as_ref(),
|
|
59
62
|
_ => panic!("expected ReadableStream opaque"),
|
|
60
63
|
};
|
|
61
|
-
let reader_val = stream
|
|
62
|
-
.get_method("getReader")
|
|
63
|
-
.expect("getReader")(&[]);
|
|
64
|
+
let reader_val = stream.get_method("getReader").expect("getReader")(&[]);
|
|
64
65
|
let reader = match reader_val {
|
|
65
66
|
Value::Opaque(r) => r,
|
|
66
67
|
_ => panic!("expected reader opaque, got {:?}", reader_val),
|