@tishlang/tish 1.13.2 → 2.0.1
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/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +1 -0
- package/crates/tish/Cargo.toml +11 -3
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cli_help.rs +15 -4
- package/crates/tish/src/main.rs +93 -21
- package/crates/tish/src/repl_completion.rs +0 -1
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +402 -91
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/src/ast.rs +37 -8
- package/crates/tish_builtins/Cargo.toml +2 -0
- package/crates/tish_builtins/src/array.rs +375 -13
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +59 -19
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +86 -6
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +5 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +2 -2
- package/crates/tish_builtins/src/string.rs +19 -20
- package/crates/tish_builtins/src/symbol.rs +1 -1
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/src/chunk.rs +69 -1
- package/crates/tish_bytecode/src/compiler.rs +933 -89
- package/crates/tish_bytecode/src/encoding.rs +2 -0
- package/crates/tish_bytecode/src/lib.rs +2 -1
- package/crates/tish_bytecode/src/opcode.rs +47 -4
- package/crates/tish_bytecode/src/serialize.rs +31 -1
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +2334 -349
- package/crates/tish_compile/src/infer.rs +1395 -6
- package/crates/tish_compile/src/lib.rs +50 -8
- package/crates/tish_compile/src/resolve.rs +584 -21
- package/crates/tish_compile/src/types.rs +106 -2
- package/crates/tish_compile_js/src/codegen.rs +67 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +64 -0
- package/crates/tish_core/Cargo.toml +7 -1
- package/crates/tish_core/src/console_style.rs +11 -1
- package/crates/tish_core/src/json.rs +81 -38
- package/crates/tish_core/src/lib.rs +3 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/value.rs +679 -25
- package/crates/tish_core/src/vmref.rs +13 -8
- package/crates/tish_cranelift/src/link.rs +17 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +1 -0
- package/crates/tish_eval/Cargo.toml +6 -0
- package/crates/tish_eval/src/eval.rs +665 -117
- package/crates/tish_eval/src/http.rs +4 -1
- package/crates/tish_eval/src/natives.rs +165 -13
- package/crates/tish_eval/src/value.rs +31 -13
- package/crates/tish_eval/src/value_convert.rs +10 -4
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/src/lib.rs +61 -5
- package/crates/tish_lexer/src/lib.rs +397 -9
- package/crates/tish_lexer/src/token.rs +7 -0
- package/crates/tish_lint/src/lib.rs +2 -10
- package/crates/tish_lsp/src/import_goto.rs +2 -0
- package/crates/tish_lsp/src/main.rs +439 -26
- package/crates/tish_native/src/build.rs +55 -1
- package/crates/tish_opt/src/lib.rs +126 -23
- package/crates/tish_parser/src/lib.rs +55 -1
- package/crates/tish_parser/src/parser.rs +456 -34
- package/crates/tish_pg/src/lib.rs +3 -3
- package/crates/tish_resolve/src/lib.rs +99 -59
- package/crates/tish_runtime/Cargo.toml +4 -0
- package/crates/tish_runtime/src/http.rs +66 -17
- package/crates/tish_runtime/src/http_fetch.rs +29 -8
- package/crates/tish_runtime/src/http_hyper.rs +25 -2
- package/crates/tish_runtime/src/lib.rs +299 -44
- package/crates/tish_runtime/src/promise.rs +328 -18
- package/crates/tish_runtime/src/timers.rs +13 -7
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +35 -18
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +2 -2
- package/crates/tish_ui/src/jsx.rs +10 -0
- package/crates/tish_ui/src/runtime/hooks.rs +19 -15
- package/crates/tish_ui/src/runtime/mod.rs +15 -12
- package/crates/tish_vm/Cargo.toml +14 -1
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +2 -0
- package/crates/tish_vm/src/vm.rs +1546 -202
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_wasm/src/lib.rs +6 -2
- package/crates/tish_wasm_runtime/src/gpu.rs +17 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/lib.rs +2 -2
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +1 -1
- 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
|
@@ -82,6 +82,20 @@ fn unregister(id: u32) {
|
|
|
82
82
|
CONNS.lock().unwrap().remove(&id);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/// Max simultaneous WebSocket connections. Each accepted conn registers in `CONNS` and
|
|
86
|
+
/// spawns tasks + channels, freed only on close — unbounded accepts exhaust tasks/memory.
|
|
87
|
+
/// Override with `TISH_WS_MAX_CONNS`; default 10000.
|
|
88
|
+
fn max_ws_connections() -> usize {
|
|
89
|
+
use std::sync::OnceLock;
|
|
90
|
+
static MAX: OnceLock<usize> = OnceLock::new();
|
|
91
|
+
*MAX.get_or_init(|| {
|
|
92
|
+
std::env::var("TISH_WS_MAX_CONNS")
|
|
93
|
+
.ok()
|
|
94
|
+
.and_then(|v| v.parse().ok())
|
|
95
|
+
.unwrap_or(10_000)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
85
99
|
fn conn_send(id: u32, data: String) -> bool {
|
|
86
100
|
let guard = match CONNS.lock() {
|
|
87
101
|
Ok(g) => g,
|
|
@@ -105,7 +119,7 @@ fn conn_receive(id: u32) -> Option<String> {
|
|
|
105
119
|
/// Uses try_recv in a loop to avoid holding CONNS lock while blocking (prevents deadlock
|
|
106
120
|
/// when connection closes and tokio task needs to unregister).
|
|
107
121
|
fn conn_receive_timeout(id: u32, timeout_ms: u64) -> Option<String> {
|
|
108
|
-
let timeout_ms = timeout_ms.min(
|
|
122
|
+
let timeout_ms = timeout_ms.min(3_600_000);
|
|
109
123
|
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
|
|
110
124
|
let poll_interval = Duration::from_millis(50);
|
|
111
125
|
loop {
|
|
@@ -149,11 +163,9 @@ fn conn_id_from_value(v: &Value) -> Option<u32> {
|
|
|
149
163
|
Value::Object(o) => {
|
|
150
164
|
let b = o.borrow();
|
|
151
165
|
// Direct conn: { _id, send, ... }
|
|
152
|
-
if let Some(
|
|
153
|
-
if
|
|
154
|
-
|
|
155
|
-
return Some(*n as u32);
|
|
156
|
-
}
|
|
166
|
+
if let Some(Value::Number(n)) = b.strings.get("_id") {
|
|
167
|
+
if n.is_finite() && *n >= 0.0 {
|
|
168
|
+
return Some(*n as u32);
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
// Wrapper: { ws: conn, ... }
|
|
@@ -168,7 +180,7 @@ fn conn_id_from_value(v: &Value) -> Option<u32> {
|
|
|
168
180
|
|
|
169
181
|
/// Native broadcast: send data to all conns in array except `except`. Avoids Tish-side method calls.
|
|
170
182
|
pub fn ws_broadcast_native(args: &[Value]) -> Value {
|
|
171
|
-
let conns = match args.
|
|
183
|
+
let conns = match args.first() {
|
|
172
184
|
Some(Value::Array(a)) => a.borrow().clone(),
|
|
173
185
|
_ => return Value::Null,
|
|
174
186
|
};
|
|
@@ -232,7 +244,7 @@ fn conn_object(id: u32) -> Value {
|
|
|
232
244
|
.first()
|
|
233
245
|
.and_then(|v| match v {
|
|
234
246
|
Value::Number(n) if n.is_finite() && *n >= 0.0 => {
|
|
235
|
-
Some((*n as u64).min(
|
|
247
|
+
Some((*n as u64).min(3_600_000))
|
|
236
248
|
}
|
|
237
249
|
_ => None,
|
|
238
250
|
})
|
|
@@ -366,6 +378,11 @@ pub fn web_socket_server_listen(args: &[Value]) -> Value {
|
|
|
366
378
|
Ok(s) => s,
|
|
367
379
|
Err(_) => break,
|
|
368
380
|
};
|
|
381
|
+
// Cap total connections — unbounded accepts would exhaust tasks/memory.
|
|
382
|
+
if CONNS.lock().map(|c| c.len()).unwrap_or(0) >= max_ws_connections() {
|
|
383
|
+
drop(stream);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
369
386
|
let ws_stream = match tokio_tungstenite::accept_async(stream).await {
|
|
370
387
|
Ok(ws) => {
|
|
371
388
|
eprintln!(
|
|
@@ -446,7 +463,7 @@ pub fn web_socket_server_accept_timeout(args: &[Value]) -> Value {
|
|
|
446
463
|
_ => return Value::Null,
|
|
447
464
|
};
|
|
448
465
|
let timeout_ms = match args.get(1) {
|
|
449
|
-
Some(Value::Number(n)) if n.is_finite() && *n >= 0.0 => (*n as u64).min(
|
|
466
|
+
Some(Value::Number(n)) if n.is_finite() && *n >= 0.0 => (*n as u64).min(3_600_000),
|
|
450
467
|
_ => 100,
|
|
451
468
|
};
|
|
452
469
|
let mut map = match SERVER_RECV.lock() {
|
|
@@ -520,7 +537,7 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
520
537
|
}
|
|
521
538
|
clients_listen.borrow_mut().push(ws.clone());
|
|
522
539
|
if let Value::Function(f) = cb {
|
|
523
|
-
let _ = f(&[ws]);
|
|
540
|
+
let _ = f.call(&[ws]);
|
|
524
541
|
}
|
|
525
542
|
}
|
|
526
543
|
Value::Null
|
|
@@ -584,7 +601,7 @@ mod tests {
|
|
|
584
601
|
for _ in 0..50 {
|
|
585
602
|
let recv_fn = wso.borrow().strings.get("receive").cloned();
|
|
586
603
|
if let Some(Value::Function(rf)) = recv_fn {
|
|
587
|
-
let msg = rf(&[]);
|
|
604
|
+
let msg = rf.call(&[]);
|
|
588
605
|
if !matches!(msg, Value::Null) {
|
|
589
606
|
let data = match msg {
|
|
590
607
|
Value::Object(ev) => ev
|
|
@@ -598,7 +615,7 @@ mod tests {
|
|
|
598
615
|
if let Some(Value::Function(sf)) =
|
|
599
616
|
wso.borrow().strings.get("send").cloned()
|
|
600
617
|
{
|
|
601
|
-
let _ = sf(&[Value::String(data.into())]);
|
|
618
|
+
let _ = sf.call(&[Value::String(data.into())]);
|
|
602
619
|
}
|
|
603
620
|
break;
|
|
604
621
|
}
|
|
@@ -619,7 +636,7 @@ mod tests {
|
|
|
619
636
|
let Value::Function(send_f) = send else {
|
|
620
637
|
panic!("no send");
|
|
621
638
|
};
|
|
622
|
-
let _ = send_f(&[Value::String("hello".into())]);
|
|
639
|
+
let _ = send_f.call(&[Value::String("hello".into())]);
|
|
623
640
|
|
|
624
641
|
let recv = co.borrow().strings.get("receive").cloned().unwrap();
|
|
625
642
|
let Value::Function(recv_f) = recv else {
|
|
@@ -627,7 +644,7 @@ mod tests {
|
|
|
627
644
|
};
|
|
628
645
|
let mut got = Value::Null;
|
|
629
646
|
for _ in 0..100 {
|
|
630
|
-
got = recv_f(&[]);
|
|
647
|
+
got = recv_f.call(&[]);
|
|
631
648
|
if !matches!(got, Value::Null) {
|
|
632
649
|
break;
|
|
633
650
|
}
|
|
@@ -673,7 +690,7 @@ mod tests {
|
|
|
673
690
|
};
|
|
674
691
|
// Poll until we get join
|
|
675
692
|
for _ in 0..200 {
|
|
676
|
-
let msg = rf(&[]);
|
|
693
|
+
let msg = rf.call(&[]);
|
|
677
694
|
if !matches!(msg, Value::Null) {
|
|
678
695
|
let data = match &msg {
|
|
679
696
|
Value::Object(ev) => ev
|
|
@@ -710,7 +727,7 @@ mod tests {
|
|
|
710
727
|
panic!("no send");
|
|
711
728
|
};
|
|
712
729
|
let join_msg = r#"{"type":"join","sessionId":"default","role":"agent","laneId":"ai-a"}"#;
|
|
713
|
-
let _ = send_f(&[Value::String(join_msg.into())]);
|
|
730
|
+
let _ = send_f.call(&[Value::String(join_msg.into())]);
|
|
714
731
|
|
|
715
732
|
// Client uses receiveTimeout like the agent
|
|
716
733
|
let recv_timeout = co
|
|
@@ -724,7 +741,7 @@ mod tests {
|
|
|
724
741
|
};
|
|
725
742
|
let timeout_arg = Value::Number(2000.0);
|
|
726
743
|
|
|
727
|
-
let got1 = recv_timeout_f(&[timeout_arg.clone()]);
|
|
744
|
+
let got1 = recv_timeout_f.call(&[timeout_arg.clone()]);
|
|
728
745
|
let Value::Object(ev1) = got1 else {
|
|
729
746
|
panic!("first recv: expected object, got {:?}", got1);
|
|
730
747
|
};
|
|
@@ -740,7 +757,7 @@ mod tests {
|
|
|
740
757
|
data1
|
|
741
758
|
);
|
|
742
759
|
|
|
743
|
-
let got2 = recv_timeout_f(&[timeout_arg]);
|
|
760
|
+
let got2 = recv_timeout_f.call(&[timeout_arg]);
|
|
744
761
|
let Value::Object(ev2) = got2 else {
|
|
745
762
|
panic!("second recv: expected object, got {:?}", got2);
|
|
746
763
|
};
|
|
@@ -65,7 +65,7 @@ fn fetch_readable_stream_read_chunks() {
|
|
|
65
65
|
Value::Opaque(s) => s.as_ref(),
|
|
66
66
|
_ => panic!("expected ReadableStream opaque"),
|
|
67
67
|
};
|
|
68
|
-
let reader_val = stream.get_method("getReader").expect("getReader")(&[]);
|
|
68
|
+
let reader_val = stream.get_method("getReader").expect("getReader").call(&[]);
|
|
69
69
|
let reader = match reader_val {
|
|
70
70
|
Value::Opaque(r) => r,
|
|
71
71
|
_ => panic!("expected reader opaque, got {:?}", reader_val),
|
|
@@ -73,7 +73,7 @@ fn fetch_readable_stream_read_chunks() {
|
|
|
73
73
|
|
|
74
74
|
let mut acc = Vec::new();
|
|
75
75
|
loop {
|
|
76
|
-
let read_p = reader.get_method("read").expect("read")(&[]);
|
|
76
|
+
let read_p = reader.get_method("read").expect("read").call(&[]);
|
|
77
77
|
let chunk = await_promise(read_p);
|
|
78
78
|
let (done, chunk_bytes) = match chunk {
|
|
79
79
|
Value::Object(o) => {
|
|
@@ -142,6 +142,11 @@ fn collect_fun_decl_names_stmt(stmt: &Statement, names: &mut HashSet<String>) {
|
|
|
142
142
|
collect_fun_decl_names_stmt(s, names);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
Statement::Multi { statements, .. } => {
|
|
146
|
+
for s in statements {
|
|
147
|
+
collect_fun_decl_names_stmt(s, names);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
145
150
|
Statement::VarDecl { init, .. } => {
|
|
146
151
|
if let Some(e) = init {
|
|
147
152
|
collect_fun_decl_names_expr(e, names);
|
|
@@ -315,6 +320,9 @@ fn collect_fun_decl_names_expr(expr: &Expr, names: &mut HashSet<String>) {
|
|
|
315
320
|
Expr::Await { operand, .. } | Expr::TypeOf { operand, .. } => {
|
|
316
321
|
collect_fun_decl_names_expr(operand, names);
|
|
317
322
|
}
|
|
323
|
+
Expr::Delete { target, .. } => {
|
|
324
|
+
collect_fun_decl_names_expr(target, names);
|
|
325
|
+
}
|
|
318
326
|
Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. } => {
|
|
319
327
|
collect_fun_decl_names_expr(value, names);
|
|
320
328
|
}
|
|
@@ -537,6 +545,7 @@ fn stmt_contains_jsx(stmt: &tishlang_ast::Statement) -> bool {
|
|
|
537
545
|
use tishlang_ast::{ExportDeclaration, Statement};
|
|
538
546
|
match stmt {
|
|
539
547
|
Statement::Block { statements, .. } => statements.iter().any(stmt_contains_jsx),
|
|
548
|
+
Statement::Multi { statements, .. } => statements.iter().any(stmt_contains_jsx),
|
|
540
549
|
Statement::VarDecl { init, .. } => init.as_ref().is_some_and(expr_contains_jsx),
|
|
541
550
|
Statement::VarDeclDestructure { init, .. } => expr_contains_jsx(init),
|
|
542
551
|
Statement::ExprStmt { expr, .. } => expr_contains_jsx(expr),
|
|
@@ -653,6 +662,7 @@ fn expr_contains_jsx(expr: &Expr) -> bool {
|
|
|
653
662
|
Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_contains_jsx),
|
|
654
663
|
Expr::Await { operand, .. } => expr_contains_jsx(operand),
|
|
655
664
|
Expr::TypeOf { operand, .. } => expr_contains_jsx(operand),
|
|
665
|
+
Expr::Delete { target, .. } => expr_contains_jsx(target),
|
|
656
666
|
Expr::PostfixInc { .. }
|
|
657
667
|
| Expr::PrefixInc { .. }
|
|
658
668
|
| Expr::PostfixDec { .. }
|
|
@@ -17,13 +17,16 @@ pub type RootId = u64;
|
|
|
17
17
|
/// First root: `install_thread_local_host` and `native_create_root` without an id argument.
|
|
18
18
|
pub const LEGACY_ROOT_ID: RootId = 1;
|
|
19
19
|
|
|
20
|
+
/// Shared, interior-mutable handle to one root's host backend.
|
|
21
|
+
type HostHandle = Rc<RefCell<Box<dyn Host>>>;
|
|
22
|
+
|
|
20
23
|
thread_local! {
|
|
21
24
|
static HOOKS: RefCell<HashMap<RootId, HookState>> = RefCell::new(HashMap::new());
|
|
22
|
-
static CURRENT_ROOT: Cell<Option<RootId>> = Cell::new(None);
|
|
23
|
-
static HOSTS: RefCell<HashMap<RootId,
|
|
24
|
-
static NEXT_DYNAMIC_ROOT_ID: Cell<RootId> = Cell::new(2);
|
|
25
|
-
static IN_FLUSH: Cell<bool> = Cell::new(false);
|
|
26
|
-
static IN_EFFECT_FLUSH: Cell<bool> = Cell::new(false);
|
|
25
|
+
static CURRENT_ROOT: Cell<Option<RootId>> = const { Cell::new(None) };
|
|
26
|
+
static HOSTS: RefCell<HashMap<RootId, HostHandle>> = RefCell::new(HashMap::new());
|
|
27
|
+
static NEXT_DYNAMIC_ROOT_ID: Cell<RootId> = const { Cell::new(2) };
|
|
28
|
+
static IN_FLUSH: Cell<bool> = const { Cell::new(false) };
|
|
29
|
+
static IN_EFFECT_FLUSH: Cell<bool> = const { Cell::new(false) };
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
/// Allocate an id for an additional in-process window (starts at 2; 1 is legacy primary).
|
|
@@ -121,6 +124,9 @@ struct PendingEffect {
|
|
|
121
124
|
new_deps: Vec<Value>,
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
/// Per-slot last dependency snapshot and cached value for `useMemo`.
|
|
128
|
+
type MemoCache = Rc<RefCell<Vec<Option<(Vec<Value>, Value)>>>>;
|
|
129
|
+
|
|
124
130
|
/// Hook storage for one `createRoot().render(App)` tree.
|
|
125
131
|
pub struct HookState {
|
|
126
132
|
pub state_slots: Rc<RefCell<Vec<Value>>>,
|
|
@@ -129,7 +135,7 @@ pub struct HookState {
|
|
|
129
135
|
pub root_vnode: Option<Value>,
|
|
130
136
|
pub flush_scheduled: bool,
|
|
131
137
|
/// Per-slot: last dependency tuple snapshot and cached value from `useMemo`.
|
|
132
|
-
pub memo_cache:
|
|
138
|
+
pub memo_cache: MemoCache,
|
|
133
139
|
pub memo_cursor: usize,
|
|
134
140
|
effect_cells: Rc<RefCell<Vec<EffectCell>>>,
|
|
135
141
|
effect_cursor: usize,
|
|
@@ -165,10 +171,8 @@ impl Default for HookState {
|
|
|
165
171
|
|
|
166
172
|
fn run_all_effect_cleanups(cells: &RefCell<Vec<EffectCell>>) {
|
|
167
173
|
for cell in cells.borrow_mut().iter_mut() {
|
|
168
|
-
if let Some(
|
|
169
|
-
|
|
170
|
-
let _ = f(&[]);
|
|
171
|
-
}
|
|
174
|
+
if let Some(Value::Function(f)) = cell.cleanup.take() {
|
|
175
|
+
let _ = f.call(&[]);
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
}
|
|
@@ -252,7 +256,7 @@ pub fn native_use_state(args: &[Value]) -> Value {
|
|
|
252
256
|
}
|
|
253
257
|
let prev = st.state_slots.borrow()[idx].clone();
|
|
254
258
|
let new_v = match &arg {
|
|
255
|
-
Value::Function(f) => f(&
|
|
259
|
+
Value::Function(f) => f.call(std::slice::from_ref(&prev)),
|
|
256
260
|
_ => arg.clone(),
|
|
257
261
|
};
|
|
258
262
|
if memo_dep_eq(&prev, &new_v) {
|
|
@@ -301,7 +305,7 @@ pub fn native_use_memo(args: &[Value]) -> Value {
|
|
|
301
305
|
if reuse {
|
|
302
306
|
return c[i].as_ref().unwrap().1.clone();
|
|
303
307
|
}
|
|
304
|
-
let produced = factory(&[]);
|
|
308
|
+
let produced = factory.call(&[]);
|
|
305
309
|
c[i] = Some((deps, produced.clone()));
|
|
306
310
|
produced
|
|
307
311
|
})
|
|
@@ -419,10 +423,10 @@ fn flush_pending_effects_for(
|
|
|
419
423
|
cells[p.slot].cleanup.take()
|
|
420
424
|
};
|
|
421
425
|
if let Some(Value::Function(f)) = cleanup_fn {
|
|
422
|
-
let _ = f(&[]);
|
|
426
|
+
let _ = f.call(&[]);
|
|
423
427
|
}
|
|
424
428
|
let run_result = if let Value::Function(f) = &p.effect_fn {
|
|
425
|
-
f(&[])
|
|
429
|
+
f.call(&[])
|
|
426
430
|
} else {
|
|
427
431
|
Value::Null
|
|
428
432
|
};
|
|
@@ -548,7 +552,7 @@ fn drain_flush_queue_on_current_thread() {
|
|
|
548
552
|
});
|
|
549
553
|
|
|
550
554
|
if let Some(f) = app_fn {
|
|
551
|
-
let tree = f(&[]);
|
|
555
|
+
let tree = f.call(&[]);
|
|
552
556
|
HOOKS.with(|h| {
|
|
553
557
|
if let Some(st) = h.borrow_mut().get_mut(&root_id) {
|
|
554
558
|
st.root_vnode = Some(tree.clone());
|
|
@@ -29,7 +29,7 @@ pub fn fragment_value() -> Value {
|
|
|
29
29
|
/// Returns true if `tag` refers to [`fragment_value`].
|
|
30
30
|
pub fn is_fragment_tag(tag: &Value) -> bool {
|
|
31
31
|
match tag {
|
|
32
|
-
Value::String(s) => s.
|
|
32
|
+
Value::String(s) => s.as_str() == FRAGMENT_SENTINEL,
|
|
33
33
|
Value::Symbol(s) => s.registry_key.as_deref() == Some("tish.fragment"),
|
|
34
34
|
_ => false,
|
|
35
35
|
}
|
|
@@ -46,17 +46,24 @@ pub fn ui_text(args: &[Value]) -> Value {
|
|
|
46
46
|
|
|
47
47
|
/// Vnode factory: `h(tag, props, children)` (Lattish-compatible shape).
|
|
48
48
|
pub fn ui_h(args: &[Value]) -> Value {
|
|
49
|
-
let tag = args.
|
|
49
|
+
let tag = args.first().cloned().unwrap_or(Value::Null);
|
|
50
50
|
let props = args.get(1).cloned().unwrap_or(Value::Null);
|
|
51
51
|
let children_arg = args.get(2).cloned().unwrap_or(Value::Null);
|
|
52
52
|
|
|
53
53
|
let children_vec = normalize_children_list(children_arg);
|
|
54
54
|
|
|
55
55
|
if let Value::Function(f) = &tag {
|
|
56
|
-
let mut merged = if matches!(props, Value::Null) {
|
|
56
|
+
let mut merged: ObjectMap = if matches!(props, Value::Null) {
|
|
57
57
|
ObjectMap::default()
|
|
58
58
|
} else if let Value::Object(obj) = props {
|
|
59
|
-
|
|
59
|
+
// `ObjectData.strings` is an insertion-ordered `PropMap`; `Value::object` takes an
|
|
60
|
+
// `ObjectMap` (`AHashMap`). Copy the entries across (object property order is not
|
|
61
|
+
// observable for a merged props map here).
|
|
62
|
+
obj.borrow()
|
|
63
|
+
.strings
|
|
64
|
+
.iter()
|
|
65
|
+
.map(|(k, v)| (Arc::clone(k), v.clone()))
|
|
66
|
+
.collect()
|
|
60
67
|
} else {
|
|
61
68
|
ObjectMap::default()
|
|
62
69
|
};
|
|
@@ -66,7 +73,7 @@ pub fn ui_h(args: &[Value]) -> Value {
|
|
|
66
73
|
Value::Array(VmRef::new(children_vec.clone())),
|
|
67
74
|
);
|
|
68
75
|
}
|
|
69
|
-
return f(&[Value::object(merged)]);
|
|
76
|
+
return f.call(&[Value::object(merged)]);
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
if is_fragment_tag(&tag) {
|
|
@@ -74,7 +81,7 @@ pub fn ui_h(args: &[Value]) -> Value {
|
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
let tag_str: Arc<str> = match tag {
|
|
77
|
-
Value::String(s) => s,
|
|
84
|
+
Value::String(s) => Arc::from(s.as_str()),
|
|
78
85
|
_ => return Value::Null,
|
|
79
86
|
};
|
|
80
87
|
|
|
@@ -105,7 +112,7 @@ fn flatten_vnode_children(items: &[Value]) -> Vec<Value> {
|
|
|
105
112
|
|
|
106
113
|
fn vnode_element(tag: Arc<str>, props: Value, children: Vec<Value>) -> Value {
|
|
107
114
|
let mut m = ObjectMap::default();
|
|
108
|
-
m.insert(Arc::from("tag"), Value::String(tag));
|
|
115
|
+
m.insert(Arc::from("tag"), Value::String(tag.as_ref().into()));
|
|
109
116
|
m.insert(
|
|
110
117
|
Arc::from("props"),
|
|
111
118
|
if matches!(props, Value::Null) {
|
|
@@ -143,15 +150,11 @@ pub trait Host {
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
/// No-op / test host that only stores the last committed tree.
|
|
153
|
+
#[derive(Default)]
|
|
146
154
|
pub struct HeadlessHost {
|
|
147
155
|
pub last: Option<Value>,
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
impl Default for HeadlessHost {
|
|
151
|
-
fn default() -> Self {
|
|
152
|
-
Self { last: None }
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
158
|
|
|
156
159
|
impl Host for HeadlessHost {
|
|
157
160
|
fn commit_root(&mut self, vnode: &Value) {
|
|
@@ -8,7 +8,10 @@ license-file = { workspace = true }
|
|
|
8
8
|
repository = { workspace = true }
|
|
9
9
|
[features]
|
|
10
10
|
default = []
|
|
11
|
-
regex
|
|
11
|
+
# RegExp needs the runtime's regex impls (regexp_new / regexp_test / regexp_exec and the
|
|
12
|
+
# regex-aware String methods) so the VM routes through the SAME code as the rust backend.
|
|
13
|
+
# Mirror the fs/http pattern: pull in the optional runtime crate + its regex feature.
|
|
14
|
+
regex = ["tishlang_core/regex", "dep:tishlang_runtime", "tishlang_runtime/regex"]
|
|
12
15
|
# Propagate `send-values` so that every native-function closure we build
|
|
13
16
|
# in the VM (array / string / object methods) picks up the right
|
|
14
17
|
# `Rc<dyn Fn>` vs `Arc<dyn Fn + Send + Sync>` wrapper at compile time.
|
|
@@ -20,6 +23,7 @@ wasm = ["dep:wasm-bindgen"]
|
|
|
20
23
|
# (some Cargo/cache combinations only saw `tishlang_runtime/http` and built VM stubs without http).
|
|
21
24
|
fs = ["dep:tishlang_runtime", "tishlang_runtime/fs"]
|
|
22
25
|
process = ["dep:tishlang_runtime", "tishlang_runtime/process"]
|
|
26
|
+
tty = ["dep:tishlang_runtime", "tishlang_runtime/tty"]
|
|
23
27
|
# Timer globals + `tish:timers` LoadNativeExport (uses tishlang_runtime TLS timer registry).
|
|
24
28
|
timers = ["dep:tishlang_runtime"]
|
|
25
29
|
# Any HTTP build needs Send-safe values so handlers can be dispatched
|
|
@@ -45,3 +49,12 @@ tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
|
|
|
45
49
|
|
|
46
50
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
47
51
|
getrandom = { version = "0.4", features = ["wasm_js"] }
|
|
52
|
+
|
|
53
|
+
# Numeric JIT (native codegen slice 1). Not built for wasm — cranelift-jit emits host
|
|
54
|
+
# machine code and cannot target wasm32. The wasm/wasi VM keeps the interpreter path.
|
|
55
|
+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
56
|
+
cranelift = "0.130"
|
|
57
|
+
cranelift-jit = "0.130"
|
|
58
|
+
cranelift-module = "0.130"
|
|
59
|
+
cranelift-native = "0.130"
|
|
60
|
+
stacker = "0.1"
|