@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.
- package/Cargo.toml +49 -0
- package/LICENSE +13 -0
- package/README.md +138 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +610 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +54 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +565 -0
- package/crates/tish/src/main.rs +781 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/integration_test.rs +1095 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +620 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +20 -0
- package/crates/tish_builtins/src/array.rs +441 -0
- package/crates/tish_builtins/src/construct.rs +159 -0
- package/crates/tish_builtins/src/globals.rs +213 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/lib.rs +16 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +647 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +96 -0
- package/crates/tish_bytecode/src/compiler.rs +1760 -0
- package/crates/tish_bytecode/src/encoding.rs +100 -0
- package/crates/tish_bytecode/src/lib.rs +19 -0
- package/crates/tish_bytecode/src/opcode.rs +142 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +26 -0
- package/crates/tish_compile/src/codegen.rs +5332 -0
- package/crates/tish_compile/src/infer.rs +292 -0
- package/crates/tish_compile/src/lib.rs +164 -0
- package/crates/tish_compile/src/resolve.rs +1388 -0
- package/crates/tish_compile/src/types.rs +501 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +871 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +26 -0
- package/crates/tish_core/src/console_style.rs +160 -0
- package/crates/tish_core/src/json.rs +387 -0
- package/crates/tish_core/src/lib.rs +17 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +696 -0
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +117 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +45 -0
- package/crates/tish_eval/src/eval.rs +3717 -0
- package/crates/tish_eval/src/http.rs +188 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +399 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +318 -0
- package/crates/tish_eval/src/value_convert.rs +111 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2101 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +716 -0
- package/crates/tish_lexer/src/token.rs +163 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +289 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +562 -0
- package/crates/tish_lsp/src/main.rs +1046 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +427 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +943 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +332 -0
- package/crates/tish_parser/src/parser.rs +2304 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3561 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +96 -0
- package/crates/tish_runtime/src/http.rs +1298 -0
- package/crates/tish_runtime/src/http_fetch.rs +471 -0
- 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 +1192 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +248 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +166 -0
- package/crates/tish_runtime/src/ws.rs +761 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +682 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +569 -0
- package/crates/tish_ui/src/runtime/mod.rs +180 -0
- package/crates/tish_vm/Cargo.toml +47 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +2192 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +424 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +268 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
//! Minimal hook state: `useState`, `useMemo`, and render flush (Lattish-style cursor reset).
|
|
2
|
+
//! Supports multiple independent roots (`RootId`) in one thread.
|
|
3
|
+
|
|
4
|
+
use std::cell::{Cell, RefCell};
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
use std::rc::Rc;
|
|
7
|
+
|
|
8
|
+
use std::sync::Arc;
|
|
9
|
+
|
|
10
|
+
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
11
|
+
|
|
12
|
+
use super::Host;
|
|
13
|
+
|
|
14
|
+
/// Opaque id for one `createRoot().render(App)` tree in this thread.
|
|
15
|
+
pub type RootId = u64;
|
|
16
|
+
|
|
17
|
+
/// First root: `install_thread_local_host` and `native_create_root` without an id argument.
|
|
18
|
+
pub const LEGACY_ROOT_ID: RootId = 1;
|
|
19
|
+
|
|
20
|
+
thread_local! {
|
|
21
|
+
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, Rc<RefCell<Box<dyn Host>>>>> = RefCell::new(HashMap::new());
|
|
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);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Allocate an id for an additional in-process window (starts at 2; 1 is legacy primary).
|
|
30
|
+
pub fn alloc_root_id() -> RootId {
|
|
31
|
+
NEXT_DYNAMIC_ROOT_ID.with(|n| {
|
|
32
|
+
let id = n.get();
|
|
33
|
+
n.set(id.saturating_add(1).max(2));
|
|
34
|
+
id
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn ensure_hook_entry(root_id: RootId) {
|
|
39
|
+
HOOKS.with(|h| {
|
|
40
|
+
h.borrow_mut().entry(root_id).or_default();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Install the host for a specific root. Replaces any previous host for that id.
|
|
45
|
+
pub fn install_host_for_root(root_id: RootId, host: Box<dyn Host>) {
|
|
46
|
+
ensure_hook_entry(root_id);
|
|
47
|
+
HOSTS.with(|h| {
|
|
48
|
+
h.borrow_mut()
|
|
49
|
+
.insert(root_id, Rc::new(RefCell::new(host)));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Legacy: install host for [`LEGACY_ROOT_ID`] (same as `macos.run` / single-window tools).
|
|
54
|
+
#[allow(dead_code)] // Emitted Rust / hosts call via `tishlang_ui` re-exports; unused inside this crate.
|
|
55
|
+
pub fn install_thread_local_host(host: Box<dyn Host>) {
|
|
56
|
+
install_host_for_root(LEGACY_ROOT_ID, host);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Remove hook state and run effect cleanups. Safe to call while the host still exists (e.g. before
|
|
60
|
+
/// dropping AppKit objects that might receive async callbacks).
|
|
61
|
+
pub fn unregister_root_hooks_and_effects(root_id: RootId) {
|
|
62
|
+
HOOKS.with(|h| {
|
|
63
|
+
if let Some(st) = h.borrow_mut().remove(&root_id) {
|
|
64
|
+
run_all_effect_cleanups(st.effect_cells.as_ref());
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Drop the [`Host`] for `root_id`. Defer after `windowWillClose:` returns when the host retains the
|
|
70
|
+
/// window delegate object that is still executing that callback.
|
|
71
|
+
pub fn drop_host_for_root(root_id: RootId) {
|
|
72
|
+
HOSTS.with(|h| {
|
|
73
|
+
h.borrow_mut().remove(&root_id);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn unregister_root(root_id: RootId) {
|
|
78
|
+
unregister_root_hooks_and_effects(root_id);
|
|
79
|
+
drop_host_for_root(root_id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn with_host_for_root<R>(root_id: RootId, f: impl FnOnce(&mut dyn Host) -> R) -> Option<R> {
|
|
83
|
+
HOSTS.with(|c| {
|
|
84
|
+
let m = c.borrow();
|
|
85
|
+
let host = m.get(&root_id)?;
|
|
86
|
+
let mut host = host.try_borrow_mut().ok()?;
|
|
87
|
+
Some(f(host.as_mut()))
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Prefer [`with_host_for_root`]; kept for call sites that assume a single primary root.
|
|
92
|
+
#[allow(dead_code)]
|
|
93
|
+
pub fn with_thread_local_host<R>(f: impl FnOnce(&mut dyn Host) -> R) -> Option<R> {
|
|
94
|
+
with_host_for_root(LEGACY_ROOT_ID, f)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Root currently rendering or running hook flush (`None` outside that scope).
|
|
98
|
+
pub fn current_root_id() -> Option<RootId> {
|
|
99
|
+
CURRENT_ROOT.get()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Sets [`CURRENT_ROOT`] for the duration of `f` so `window.*` and similar APIs target this tree.
|
|
103
|
+
pub fn run_with_current_root<R>(root_id: RootId, f: impl FnOnce() -> R) -> R {
|
|
104
|
+
let prev = CURRENT_ROOT.get();
|
|
105
|
+
CURRENT_ROOT.set(Some(root_id));
|
|
106
|
+
let out = f();
|
|
107
|
+
CURRENT_ROOT.set(prev);
|
|
108
|
+
out
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// One `useEffect` slot: committed dependency snapshot and optional cleanup from the last run.
|
|
112
|
+
#[derive(Default)]
|
|
113
|
+
struct EffectCell {
|
|
114
|
+
committed_deps: Option<Vec<Value>>,
|
|
115
|
+
cleanup: Option<Value>,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
struct PendingEffect {
|
|
119
|
+
slot: usize,
|
|
120
|
+
effect_fn: Value,
|
|
121
|
+
new_deps: Vec<Value>,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Hook storage for one `createRoot().render(App)` tree.
|
|
125
|
+
pub struct HookState {
|
|
126
|
+
pub state_slots: Rc<RefCell<Vec<Value>>>,
|
|
127
|
+
pub cursor: usize,
|
|
128
|
+
pub root_app: Option<Value>,
|
|
129
|
+
pub root_vnode: Option<Value>,
|
|
130
|
+
pub flush_scheduled: bool,
|
|
131
|
+
/// Per-slot: last dependency tuple snapshot and cached value from `useMemo`.
|
|
132
|
+
pub memo_cache: Rc<RefCell<Vec<Option<(Vec<Value>, Value)>>>>,
|
|
133
|
+
pub memo_cursor: usize,
|
|
134
|
+
effect_cells: Rc<RefCell<Vec<EffectCell>>>,
|
|
135
|
+
effect_cursor: usize,
|
|
136
|
+
pending_effects: Rc<RefCell<Vec<PendingEffect>>>,
|
|
137
|
+
layout_effect_cells: Rc<RefCell<Vec<EffectCell>>>,
|
|
138
|
+
layout_effect_cursor: usize,
|
|
139
|
+
pending_layout_effects: Rc<RefCell<Vec<PendingEffect>>>,
|
|
140
|
+
ref_slots: Rc<RefCell<Vec<Value>>>,
|
|
141
|
+
ref_cursor: usize,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl Default for HookState {
|
|
145
|
+
fn default() -> Self {
|
|
146
|
+
Self {
|
|
147
|
+
state_slots: Rc::new(RefCell::new(Vec::new())),
|
|
148
|
+
cursor: 0,
|
|
149
|
+
root_app: None,
|
|
150
|
+
root_vnode: None,
|
|
151
|
+
flush_scheduled: false,
|
|
152
|
+
memo_cache: Rc::new(RefCell::new(Vec::new())),
|
|
153
|
+
memo_cursor: 0,
|
|
154
|
+
effect_cells: Rc::new(RefCell::new(Vec::new())),
|
|
155
|
+
effect_cursor: 0,
|
|
156
|
+
pending_effects: Rc::new(RefCell::new(Vec::new())),
|
|
157
|
+
layout_effect_cells: Rc::new(RefCell::new(Vec::new())),
|
|
158
|
+
layout_effect_cursor: 0,
|
|
159
|
+
pending_layout_effects: Rc::new(RefCell::new(Vec::new())),
|
|
160
|
+
ref_slots: Rc::new(RefCell::new(Vec::new())),
|
|
161
|
+
ref_cursor: 0,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn run_all_effect_cleanups(cells: &RefCell<Vec<EffectCell>>) {
|
|
167
|
+
for cell in cells.borrow_mut().iter_mut() {
|
|
168
|
+
if let Some(c) = cell.cleanup.take() {
|
|
169
|
+
if let Value::Function(f) = c {
|
|
170
|
+
let _ = f(&[]);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl HookState {
|
|
177
|
+
pub fn reset_for_new_root(&mut self) {
|
|
178
|
+
run_all_effect_cleanups(self.effect_cells.as_ref());
|
|
179
|
+
run_all_effect_cleanups(self.layout_effect_cells.as_ref());
|
|
180
|
+
self.effect_cells.borrow_mut().clear();
|
|
181
|
+
self.layout_effect_cells.borrow_mut().clear();
|
|
182
|
+
self.effect_cursor = 0;
|
|
183
|
+
self.layout_effect_cursor = 0;
|
|
184
|
+
self.pending_effects.borrow_mut().clear();
|
|
185
|
+
self.pending_layout_effects.borrow_mut().clear();
|
|
186
|
+
self.state_slots.borrow_mut().clear();
|
|
187
|
+
self.cursor = 0;
|
|
188
|
+
self.ref_slots.borrow_mut().clear();
|
|
189
|
+
self.ref_cursor = 0;
|
|
190
|
+
self.root_vnode = None;
|
|
191
|
+
self.flush_scheduled = false;
|
|
192
|
+
self.memo_cache.borrow_mut().clear();
|
|
193
|
+
self.memo_cursor = 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn memo_dep_eq(a: &Value, b: &Value) -> bool {
|
|
198
|
+
match (a, b) {
|
|
199
|
+
(Value::Number(x), Value::Number(y)) => {
|
|
200
|
+
if x.is_nan() && y.is_nan() {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
x == y
|
|
204
|
+
}
|
|
205
|
+
(Value::String(x), Value::String(y)) => x == y,
|
|
206
|
+
(Value::Bool(x), Value::Bool(y)) => x == y,
|
|
207
|
+
(Value::Null, Value::Null) => true,
|
|
208
|
+
(Value::Array(ax), Value::Array(bx)) => {
|
|
209
|
+
let ab = ax.borrow();
|
|
210
|
+
let bb = bx.borrow();
|
|
211
|
+
if ab.len() != bb.len() {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
ab.iter().zip(bb.iter()).all(|(x, y)| memo_dep_eq(x, y))
|
|
215
|
+
}
|
|
216
|
+
(Value::Object(a), Value::Object(b)) => tishlang_core::VmRef::ptr_eq(a, b),
|
|
217
|
+
_ => false,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn memo_deps_unchanged(prev: &[Value], next: &[Value]) -> bool {
|
|
222
|
+
prev.len() == next.len() && prev.iter().zip(next.iter()).all(|(a, b)| memo_dep_eq(a, b))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn root_id_for_hooks() -> RootId {
|
|
226
|
+
CURRENT_ROOT.get().unwrap_or(LEGACY_ROOT_ID)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// `useState(initial)` → `[state, setState]` as a Tish array.
|
|
230
|
+
pub fn native_use_state(args: &[Value]) -> Value {
|
|
231
|
+
let initial = args.first().cloned().unwrap_or(Value::Null);
|
|
232
|
+
let root_id = root_id_for_hooks();
|
|
233
|
+
HOOKS.with(|h| {
|
|
234
|
+
let mut map = h.borrow_mut();
|
|
235
|
+
let st = map.entry(root_id).or_default();
|
|
236
|
+
let i = st.cursor;
|
|
237
|
+
st.cursor += 1;
|
|
238
|
+
let slots = &st.state_slots.clone();
|
|
239
|
+
while i >= slots.borrow().len() {
|
|
240
|
+
slots.borrow_mut().push(initial.clone());
|
|
241
|
+
}
|
|
242
|
+
let current = slots.borrow()[i].clone();
|
|
243
|
+
let idx = i;
|
|
244
|
+
let setter = Value::native(move |a: &[Value]| {
|
|
245
|
+
let arg = a.first().cloned().unwrap_or(Value::Null);
|
|
246
|
+
HOOKS.with(|hooks| {
|
|
247
|
+
if let Some(st) = hooks.borrow_mut().get_mut(&root_id) {
|
|
248
|
+
let slot_len = st.state_slots.borrow().len();
|
|
249
|
+
if idx >= slot_len {
|
|
250
|
+
st.flush_scheduled = true;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
let prev = st.state_slots.borrow()[idx].clone();
|
|
254
|
+
let new_v = match &arg {
|
|
255
|
+
Value::Function(f) => f(&[prev.clone()]),
|
|
256
|
+
_ => arg.clone(),
|
|
257
|
+
};
|
|
258
|
+
if memo_dep_eq(&prev, &new_v) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
st.state_slots.borrow_mut()[idx] = new_v;
|
|
262
|
+
st.flush_scheduled = true;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
if !IN_FLUSH.get() && !IN_EFFECT_FLUSH.get() {
|
|
266
|
+
drain_flush_queue();
|
|
267
|
+
}
|
|
268
|
+
Value::Null
|
|
269
|
+
});
|
|
270
|
+
Value::Array(VmRef::new(vec![current, setter]))
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// `useMemo(factory, deps?)` — caches `factory()` until `deps` changes (shallow compare per slot).
|
|
275
|
+
pub fn native_use_memo(args: &[Value]) -> Value {
|
|
276
|
+
let Some(Value::Function(factory)) = args.first() else {
|
|
277
|
+
return Value::Null;
|
|
278
|
+
};
|
|
279
|
+
let factory = factory.clone();
|
|
280
|
+
let deps: Vec<Value> = match args.get(1) {
|
|
281
|
+
Some(Value::Array(a)) => a.borrow().clone(),
|
|
282
|
+
Some(other) => vec![other.clone()],
|
|
283
|
+
None => vec![],
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
let root_id = root_id_for_hooks();
|
|
287
|
+
HOOKS.with(|h| {
|
|
288
|
+
let mut map = h.borrow_mut();
|
|
289
|
+
let st = map.entry(root_id).or_default();
|
|
290
|
+
let i = st.memo_cursor;
|
|
291
|
+
st.memo_cursor += 1;
|
|
292
|
+
let cache = &st.memo_cache.clone();
|
|
293
|
+
let mut c = cache.borrow_mut();
|
|
294
|
+
while c.len() <= i {
|
|
295
|
+
c.push(None);
|
|
296
|
+
}
|
|
297
|
+
let reuse = match &c[i] {
|
|
298
|
+
Some((old_deps, _)) => memo_deps_unchanged(old_deps, &deps),
|
|
299
|
+
None => false,
|
|
300
|
+
};
|
|
301
|
+
if reuse {
|
|
302
|
+
return c[i].as_ref().unwrap().1.clone();
|
|
303
|
+
}
|
|
304
|
+
let produced = factory(&[]);
|
|
305
|
+
c[i] = Some((deps, produced.clone()));
|
|
306
|
+
produced
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// `useRef(initial)` → `{ current: initial }` (stable object identity per slot).
|
|
311
|
+
pub fn native_use_ref(args: &[Value]) -> Value {
|
|
312
|
+
let initial = args.first().cloned().unwrap_or(Value::Null);
|
|
313
|
+
let root_id = root_id_for_hooks();
|
|
314
|
+
HOOKS.with(|h| {
|
|
315
|
+
let mut map = h.borrow_mut();
|
|
316
|
+
let st = map.entry(root_id).or_default();
|
|
317
|
+
let i = st.ref_cursor;
|
|
318
|
+
st.ref_cursor += 1;
|
|
319
|
+
while i >= st.ref_slots.borrow().len() {
|
|
320
|
+
let mut m = ObjectMap::default();
|
|
321
|
+
m.insert(Arc::from("current"), initial.clone());
|
|
322
|
+
st.ref_slots.borrow_mut().push(Value::object(m));
|
|
323
|
+
}
|
|
324
|
+
let out = st.ref_slots.borrow()[i].clone();
|
|
325
|
+
out
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn queue_effect(
|
|
330
|
+
args: &[Value],
|
|
331
|
+
layout: bool,
|
|
332
|
+
) -> Value {
|
|
333
|
+
let Some(Value::Function(effect_fn)) = args.first() else {
|
|
334
|
+
return Value::Null;
|
|
335
|
+
};
|
|
336
|
+
let effect_fn = effect_fn.clone();
|
|
337
|
+
let deps: Vec<Value> = match args.get(1) {
|
|
338
|
+
Some(Value::Array(a)) => a.borrow().clone(),
|
|
339
|
+
Some(other) => vec![other.clone()],
|
|
340
|
+
None => vec![],
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
let root_id = root_id_for_hooks();
|
|
344
|
+
HOOKS.with(|h| {
|
|
345
|
+
let mut map = h.borrow_mut();
|
|
346
|
+
let st = map.entry(root_id).or_default();
|
|
347
|
+
let (cursor, cells, pending) = if layout {
|
|
348
|
+
(
|
|
349
|
+
&mut st.layout_effect_cursor,
|
|
350
|
+
&st.layout_effect_cells,
|
|
351
|
+
&st.pending_layout_effects,
|
|
352
|
+
)
|
|
353
|
+
} else {
|
|
354
|
+
(
|
|
355
|
+
&mut st.effect_cursor,
|
|
356
|
+
&st.effect_cells,
|
|
357
|
+
&st.pending_effects,
|
|
358
|
+
)
|
|
359
|
+
};
|
|
360
|
+
let i = *cursor;
|
|
361
|
+
*cursor += 1;
|
|
362
|
+
let cells = cells.clone();
|
|
363
|
+
let mut cells_b = cells.borrow_mut();
|
|
364
|
+
while cells_b.len() <= i {
|
|
365
|
+
cells_b.push(EffectCell::default());
|
|
366
|
+
}
|
|
367
|
+
let should_run = match &cells_b[i].committed_deps {
|
|
368
|
+
None => true,
|
|
369
|
+
Some(old) => !memo_deps_unchanged(old, &deps),
|
|
370
|
+
};
|
|
371
|
+
drop(cells_b);
|
|
372
|
+
|
|
373
|
+
if should_run {
|
|
374
|
+
pending.borrow_mut().push(PendingEffect {
|
|
375
|
+
slot: i,
|
|
376
|
+
effect_fn: Value::Function(effect_fn),
|
|
377
|
+
new_deps: deps,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
Value::Null
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// `useLayoutEffect(effect, deps?)` — runs synchronously after commit, before `useEffect`.
|
|
385
|
+
pub fn native_use_layout_effect(args: &[Value]) -> Value {
|
|
386
|
+
queue_effect(args, true)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// `useEffect(effect, deps?)` — runs `effect` after the host commits the tree; compares `deps` like `useMemo`.
|
|
390
|
+
/// If `effect` returns a function, it is called before the next run or on root teardown (`render` replacement / [`unregister_root`]).
|
|
391
|
+
pub fn native_use_effect(args: &[Value]) -> Value {
|
|
392
|
+
queue_effect(args, false)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn flush_pending_effects_for(
|
|
396
|
+
root_id: RootId,
|
|
397
|
+
pending_key: impl FnOnce(&mut HookState) -> &Rc<RefCell<Vec<PendingEffect>>>,
|
|
398
|
+
cells_key: impl FnOnce(&HookState) -> Rc<RefCell<Vec<EffectCell>>>,
|
|
399
|
+
) {
|
|
400
|
+
let pending: Vec<PendingEffect> = HOOKS.with(|h| {
|
|
401
|
+
h.borrow_mut()
|
|
402
|
+
.get_mut(&root_id)
|
|
403
|
+
.map(|st| std::mem::take(&mut *pending_key(st).borrow_mut()))
|
|
404
|
+
.unwrap_or_default()
|
|
405
|
+
});
|
|
406
|
+
let cells_rc = HOOKS.with(|h| h.borrow().get(&root_id).map(cells_key));
|
|
407
|
+
let Some(cells_rc) = cells_rc else {
|
|
408
|
+
return;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
for p in pending {
|
|
412
|
+
// Never hold `effect_cells` across user cleanup/effect: they may call `setState` →
|
|
413
|
+
// `drain_flush_queue` → `native_use_effect`, which must `borrow_mut` the same RefCell.
|
|
414
|
+
let cleanup_fn = {
|
|
415
|
+
let mut cells = cells_rc.borrow_mut();
|
|
416
|
+
while cells.len() <= p.slot {
|
|
417
|
+
cells.push(EffectCell::default());
|
|
418
|
+
}
|
|
419
|
+
cells[p.slot].cleanup.take()
|
|
420
|
+
};
|
|
421
|
+
if let Some(Value::Function(f)) = cleanup_fn {
|
|
422
|
+
let _ = f(&[]);
|
|
423
|
+
}
|
|
424
|
+
let run_result = if let Value::Function(f) = &p.effect_fn {
|
|
425
|
+
f(&[])
|
|
426
|
+
} else {
|
|
427
|
+
Value::Null
|
|
428
|
+
};
|
|
429
|
+
{
|
|
430
|
+
let mut cells = cells_rc.borrow_mut();
|
|
431
|
+
while cells.len() <= p.slot {
|
|
432
|
+
cells.push(EffectCell::default());
|
|
433
|
+
}
|
|
434
|
+
let cell = &mut cells[p.slot];
|
|
435
|
+
cell.cleanup = match run_result {
|
|
436
|
+
Value::Function(f) => Some(Value::Function(f)),
|
|
437
|
+
_ => None,
|
|
438
|
+
};
|
|
439
|
+
cell.committed_deps = Some(p.new_deps);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
fn flush_pending_layout_effects(root_id: RootId) {
|
|
445
|
+
IN_EFFECT_FLUSH.set(true);
|
|
446
|
+
flush_pending_effects_for(
|
|
447
|
+
root_id,
|
|
448
|
+
|st| &st.pending_layout_effects,
|
|
449
|
+
|st| st.layout_effect_cells.clone(),
|
|
450
|
+
);
|
|
451
|
+
IN_EFFECT_FLUSH.set(false);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fn flush_pending_effects(root_id: RootId) {
|
|
455
|
+
IN_EFFECT_FLUSH.set(true);
|
|
456
|
+
flush_pending_effects_for(
|
|
457
|
+
root_id,
|
|
458
|
+
|st| &st.pending_effects,
|
|
459
|
+
|st| st.effect_cells.clone(),
|
|
460
|
+
);
|
|
461
|
+
IN_EFFECT_FLUSH.set(false);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
fn parse_root_id_arg(args: &[Value]) -> RootId {
|
|
465
|
+
match args.first() {
|
|
466
|
+
Some(Value::Number(n)) if n.is_finite() && *n >= 1.0 && n.fract() == 0.0 => *n as u64,
|
|
467
|
+
_ => LEGACY_ROOT_ID,
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/// `createRoot(container?)` or `createRoot(rootId)` → `{ render: (App) => { ... } }`.
|
|
472
|
+
/// Pass a positive integer as the first argument to bind this root to a host installed via
|
|
473
|
+
/// [`install_host_for_root`].
|
|
474
|
+
pub fn native_create_root(args: &[Value]) -> Value {
|
|
475
|
+
let root_id = parse_root_id_arg(args);
|
|
476
|
+
ensure_hook_entry(root_id);
|
|
477
|
+
let render_fn = Value::native(move |app_args: &[Value]| {
|
|
478
|
+
let app = app_args.first().cloned().unwrap_or(Value::Null);
|
|
479
|
+
HOOKS.with(|h| {
|
|
480
|
+
let mut map = h.borrow_mut();
|
|
481
|
+
let st = map.entry(root_id).or_default();
|
|
482
|
+
st.reset_for_new_root();
|
|
483
|
+
st.root_app = Some(app);
|
|
484
|
+
st.flush_scheduled = true;
|
|
485
|
+
});
|
|
486
|
+
drain_flush_queue();
|
|
487
|
+
Value::Null
|
|
488
|
+
});
|
|
489
|
+
Value::object(ObjectMap::from([(
|
|
490
|
+
std::sync::Arc::from("render"),
|
|
491
|
+
render_fn,
|
|
492
|
+
)]))
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/// Request a re-render (coalesced; safe if called during flush).
|
|
496
|
+
pub fn schedule_flush() {
|
|
497
|
+
let root_id = root_id_for_hooks();
|
|
498
|
+
HOOKS.with(|h| {
|
|
499
|
+
if let Some(st) = h.borrow_mut().get_mut(&root_id) {
|
|
500
|
+
st.flush_scheduled = true;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
if IN_FLUSH.get() {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
drain_flush_queue();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn drain_flush_queue() {
|
|
510
|
+
drain_flush_queue_on_current_thread();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn drain_flush_queue_on_current_thread() {
|
|
514
|
+
loop {
|
|
515
|
+
let root_id = HOOKS.with(|h| {
|
|
516
|
+
h.borrow()
|
|
517
|
+
.iter()
|
|
518
|
+
.find(|(_, st)| st.flush_scheduled)
|
|
519
|
+
.map(|(id, _)| *id)
|
|
520
|
+
});
|
|
521
|
+
let Some(root_id) = root_id else {
|
|
522
|
+
break;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
IN_FLUSH.set(true);
|
|
526
|
+
CURRENT_ROOT.set(Some(root_id));
|
|
527
|
+
HOOKS.with(|h| {
|
|
528
|
+
if let Some(st) = h.borrow_mut().get_mut(&root_id) {
|
|
529
|
+
st.flush_scheduled = false;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
let app_fn = HOOKS.with(|h| {
|
|
534
|
+
let mut map = h.borrow_mut();
|
|
535
|
+
let st = map.get_mut(&root_id)?;
|
|
536
|
+
st.cursor = 0;
|
|
537
|
+
st.memo_cursor = 0;
|
|
538
|
+
st.ref_cursor = 0;
|
|
539
|
+
st.effect_cursor = 0;
|
|
540
|
+
st.layout_effect_cursor = 0;
|
|
541
|
+
st.pending_effects.borrow_mut().clear();
|
|
542
|
+
st.pending_layout_effects.borrow_mut().clear();
|
|
543
|
+
let app = st.root_app.clone()?;
|
|
544
|
+
let Value::Function(f) = app else {
|
|
545
|
+
return None;
|
|
546
|
+
};
|
|
547
|
+
Some(f)
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if let Some(f) = app_fn {
|
|
551
|
+
let tree = f(&[]);
|
|
552
|
+
HOOKS.with(|h| {
|
|
553
|
+
if let Some(st) = h.borrow_mut().get_mut(&root_id) {
|
|
554
|
+
st.root_vnode = Some(tree.clone());
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
let host = HOSTS.with(|hosts| hosts.borrow().get(&root_id).cloned());
|
|
558
|
+
if let Some(host) = host {
|
|
559
|
+
host.borrow_mut().commit_root(&tree);
|
|
560
|
+
}
|
|
561
|
+
IN_FLUSH.set(false);
|
|
562
|
+
flush_pending_layout_effects(root_id);
|
|
563
|
+
flush_pending_effects(root_id);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
CURRENT_ROOT.set(None);
|
|
567
|
+
IN_FLUSH.set(false);
|
|
568
|
+
}
|
|
569
|
+
}
|