@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,413 @@
1
+ //! Browser WebGPU / JS-interop FFI for the tish bytecode VM.
2
+ //!
3
+ //! Lets a tish program compiled to wasm drive the browser's WebGPU (and any
4
+ //! Web API) without hand-binding each call. The bridge is a tiny set of
5
+ //! reflection-based primitives exposed as VM globals:
6
+ //!
7
+ //! - `js_global(name)` — read a JS global (e.g. `navigator`, `GPUBufferUsage`)
8
+ //! - `js_get(handle, key)` / `js_set(handle, key, val)`
9
+ //! - `js_call(handle, method, argsArray)` — call a method (the whole WebGPU
10
+ //! command API is synchronous, so this covers it)
11
+ //! - `js_new(ctorNameOrHandle, argsArray)`
12
+ //! - `js_typeof(handle)` — debugging
13
+ //! - `f32a(arr)` / `u16a(arr)` / `u8a(arr)` — tish `number[]` → real typed array
14
+ //! - `request_animation_frame(cb)` — drive a render loop
15
+ //!
16
+ //! GPU/JS objects (device, queue, context, buffers, pipelines, textures,
17
+ //! ImageBitmaps, the host env object …) round-trip through the VM as opaque
18
+ //! [`JsHandle`] values. Async startup (requestAdapter/requestDevice/fetch/
19
+ //! createImageBitmap) is done in JS glue; the ready handles are handed to the
20
+ //! VM via the `host` global by [`start`].
21
+
22
+ use std::any::Any;
23
+ use std::cell::{Cell, RefCell};
24
+ use std::sync::Arc;
25
+
26
+ use tishlang_bytecode::deserialize;
27
+ use tishlang_core::{value_call, NativeFn, TishOpaque, Value};
28
+ use tishlang_vm::Vm;
29
+ use wasm_bindgen::prelude::*;
30
+ use wasm_bindgen::JsCast;
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Opaque JS handle
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /// Opaque tish value wrapping a browser `JsValue`. `JsValue` is `!Send`, which
37
+ /// is why `TishOpaque`'s `Send + Sync` bound is gated off in the browser
38
+ /// (`!send-values`) build — see `tish_core/src/value.rs`.
39
+ struct JsHandle(JsValue);
40
+
41
+ impl TishOpaque for JsHandle {
42
+ fn type_name(&self) -> &'static str {
43
+ "JsHandle"
44
+ }
45
+ fn get_method(&self, _name: &str) -> Option<NativeFn> {
46
+ None
47
+ }
48
+ fn as_any(&self) -> &dyn Any {
49
+ self
50
+ }
51
+ }
52
+
53
+ fn wrap(v: JsValue) -> Value {
54
+ Value::Opaque(Arc::new(JsHandle(v)))
55
+ }
56
+
57
+ fn unwrap_handle(v: &Value) -> Option<JsValue> {
58
+ match v {
59
+ Value::Opaque(o) => o.as_any().downcast_ref::<JsHandle>().map(|h| h.0.clone()),
60
+ _ => None,
61
+ }
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Marshalling tish Value <-> JsValue
66
+ // ---------------------------------------------------------------------------
67
+
68
+ /// Convert a tish value to a JS value. Objects/arrays recurse; an opaque
69
+ /// `JsHandle` is spliced in **by reference** (so descriptors can embed live GPU
70
+ /// handles, e.g. `beginRenderPass({ colorAttachments:[{ view:<handle> }] })`).
71
+ /// Functions/promises/symbols are not marshalled (return `null`).
72
+ fn value_to_js(v: &Value) -> JsValue {
73
+ match v {
74
+ Value::Number(n) => JsValue::from_f64(*n),
75
+ Value::String(s) => JsValue::from_str(s.as_ref()),
76
+ Value::Bool(b) => JsValue::from_bool(*b),
77
+ Value::Null => JsValue::NULL,
78
+ Value::Opaque(o) => match o.as_any().downcast_ref::<JsHandle>() {
79
+ Some(h) => h.0.clone(),
80
+ None => JsValue::NULL,
81
+ },
82
+ Value::Array(arr) => {
83
+ let out = js_sys::Array::new();
84
+ for item in arr.borrow().iter() {
85
+ out.push(&value_to_js(item));
86
+ }
87
+ out.into()
88
+ }
89
+ Value::Object(obj) => {
90
+ let out = js_sys::Object::new();
91
+ let b = obj.borrow();
92
+ for (k, val) in b.strings.iter() {
93
+ let _ = js_sys::Reflect::set(
94
+ &out,
95
+ &JsValue::from_str(k.as_ref()),
96
+ &value_to_js(val),
97
+ );
98
+ }
99
+ out.into()
100
+ }
101
+ _ => JsValue::NULL,
102
+ }
103
+ }
104
+
105
+ /// Convert a JS value back to tish. Primitives map directly; everything else
106
+ /// (objects, functions, typed arrays, GPU objects) becomes an opaque handle —
107
+ /// we deliberately do **not** expand JS containers into tish arrays/objects
108
+ /// (that would re-introduce boxing and lose typed-array identity).
109
+ fn js_to_value(v: JsValue) -> Value {
110
+ if v.is_null() || v.is_undefined() {
111
+ Value::Null
112
+ } else if let Some(n) = v.as_f64() {
113
+ Value::Number(n)
114
+ } else if let Some(b) = v.as_bool() {
115
+ Value::Bool(b)
116
+ } else if let Some(s) = v.as_string() {
117
+ Value::String(s.into())
118
+ } else {
119
+ wrap(v)
120
+ }
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // FFI builtins
125
+ // ---------------------------------------------------------------------------
126
+
127
+ fn ffi_js_global() -> Value {
128
+ Value::native(|args: &[Value]| {
129
+ let name = match args.first() {
130
+ Some(Value::String(s)) => s.clone(),
131
+ _ => return Value::Null,
132
+ };
133
+ match js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str(name.as_ref())) {
134
+ Ok(v) => js_to_value(v),
135
+ Err(_) => Value::Null,
136
+ }
137
+ })
138
+ }
139
+
140
+ fn ffi_js_get() -> Value {
141
+ Value::native(|args: &[Value]| {
142
+ let obj = match args.first().and_then(unwrap_handle) {
143
+ Some(o) => o,
144
+ None => return Value::Null,
145
+ };
146
+ let key = args.get(1).map(value_to_js).unwrap_or(JsValue::NULL);
147
+ match js_sys::Reflect::get(&obj, &key) {
148
+ Ok(v) => js_to_value(v),
149
+ Err(_) => Value::Null,
150
+ }
151
+ })
152
+ }
153
+
154
+ fn ffi_js_set() -> Value {
155
+ Value::native(|args: &[Value]| {
156
+ let obj = match args.first().and_then(unwrap_handle) {
157
+ Some(o) => o,
158
+ None => return Value::Null,
159
+ };
160
+ let key = args.get(1).map(value_to_js).unwrap_or(JsValue::NULL);
161
+ let val = args.get(2).map(value_to_js).unwrap_or(JsValue::NULL);
162
+ let _ = js_sys::Reflect::set(&obj, &key, &val);
163
+ Value::Null
164
+ })
165
+ }
166
+
167
+ fn ffi_js_call() -> Value {
168
+ Value::native(|args: &[Value]| {
169
+ let obj = match args.first().and_then(unwrap_handle) {
170
+ Some(o) => o,
171
+ None => return Value::Null,
172
+ };
173
+ let method = match args.get(1) {
174
+ Some(Value::String(s)) => s.clone(),
175
+ _ => return Value::Null,
176
+ };
177
+ let func = match js_sys::Reflect::get(&obj, &JsValue::from_str(method.as_ref())) {
178
+ Ok(f) => match f.dyn_into::<js_sys::Function>() {
179
+ Ok(f) => f,
180
+ Err(_) => return Value::Null,
181
+ },
182
+ Err(_) => return Value::Null,
183
+ };
184
+ let js_args = js_sys::Array::new();
185
+ if let Some(Value::Array(a)) = args.get(2) {
186
+ for item in a.borrow().iter() {
187
+ js_args.push(&value_to_js(item));
188
+ }
189
+ }
190
+ match js_sys::Reflect::apply(&func, &obj, &js_args) {
191
+ Ok(v) => js_to_value(v),
192
+ Err(_) => Value::Null,
193
+ }
194
+ })
195
+ }
196
+
197
+ fn ffi_js_new() -> Value {
198
+ Value::native(|args: &[Value]| {
199
+ let ctor: JsValue = match args.first() {
200
+ Some(Value::String(s)) => {
201
+ match js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str(s.as_ref())) {
202
+ Ok(v) => v,
203
+ Err(_) => return Value::Null,
204
+ }
205
+ }
206
+ Some(v @ Value::Opaque(_)) => match unwrap_handle(v) {
207
+ Some(h) => h,
208
+ None => return Value::Null,
209
+ },
210
+ _ => return Value::Null,
211
+ };
212
+ let ctor_fn = match ctor.dyn_into::<js_sys::Function>() {
213
+ Ok(f) => f,
214
+ Err(_) => return Value::Null,
215
+ };
216
+ let js_args = js_sys::Array::new();
217
+ if let Some(Value::Array(a)) = args.get(1) {
218
+ for item in a.borrow().iter() {
219
+ js_args.push(&value_to_js(item));
220
+ }
221
+ }
222
+ match js_sys::Reflect::construct(&ctor_fn, &js_args) {
223
+ Ok(v) => js_to_value(v),
224
+ Err(_) => Value::Null,
225
+ }
226
+ })
227
+ }
228
+
229
+ fn ffi_js_typeof() -> Value {
230
+ Value::native(|args: &[Value]| {
231
+ let v = args.first().map(value_to_js).unwrap_or(JsValue::NULL);
232
+ match v.js_typeof().as_string() {
233
+ Some(s) => Value::String(s.into()),
234
+ None => Value::Null,
235
+ }
236
+ })
237
+ }
238
+
239
+ /// `f32a(numberArray)` -> opaque `Float32Array` handle (one-shot copy). Use for
240
+ /// per-frame uniform/transform staging. Large static buffers should instead be
241
+ /// materialised host-side and passed in opaque (never boxed into a tish array).
242
+ fn ffi_f32a() -> Value {
243
+ Value::native(|args: &[Value]| {
244
+ let arr = match args.first() {
245
+ Some(Value::Array(a)) => a.clone(),
246
+ _ => return Value::Null,
247
+ };
248
+ let b = arr.borrow();
249
+ let ta = js_sys::Float32Array::new_with_length(b.len() as u32);
250
+ for (i, v) in b.iter().enumerate() {
251
+ ta.set_index(i as u32, v.as_number().unwrap_or(0.0) as f32);
252
+ }
253
+ wrap(ta.into())
254
+ })
255
+ }
256
+
257
+ fn ffi_u16a() -> Value {
258
+ Value::native(|args: &[Value]| {
259
+ let arr = match args.first() {
260
+ Some(Value::Array(a)) => a.clone(),
261
+ _ => return Value::Null,
262
+ };
263
+ let b = arr.borrow();
264
+ let ta = js_sys::Uint16Array::new_with_length(b.len() as u32);
265
+ for (i, v) in b.iter().enumerate() {
266
+ ta.set_index(i as u32, v.as_number().unwrap_or(0.0) as u16);
267
+ }
268
+ wrap(ta.into())
269
+ })
270
+ }
271
+
272
+ fn ffi_u8a() -> Value {
273
+ Value::native(|args: &[Value]| {
274
+ let arr = match args.first() {
275
+ Some(Value::Array(a)) => a.clone(),
276
+ _ => return Value::Null,
277
+ };
278
+ let b = arr.borrow();
279
+ let ta = js_sys::Uint8Array::new_with_length(b.len() as u32);
280
+ for (i, v) in b.iter().enumerate() {
281
+ ta.set_index(i as u32, v.as_number().unwrap_or(0.0) as u8);
282
+ }
283
+ wrap(ta.into())
284
+ })
285
+ }
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // requestAnimationFrame render loop
289
+ // ---------------------------------------------------------------------------
290
+
291
+ thread_local! {
292
+ static RAF_CALLBACK: RefCell<Option<Value>> = const { RefCell::new(None) };
293
+ static RAF_CLOSURE: RefCell<Option<Closure<dyn FnMut(f64)>>> = const { RefCell::new(None) };
294
+ // True while a frame is pending, so repeated request_animation_frame calls
295
+ // within one frame don't compound into runaway scheduling.
296
+ static RAF_SCHEDULED: Cell<bool> = const { Cell::new(false) };
297
+ }
298
+
299
+ /// Browser-driven per-frame entry: invoke the stored tish callback, then
300
+ /// re-arm for the next frame. We re-schedule from Rust (rather than requiring
301
+ /// the tish callback to call `request_animation_frame` again each frame) so the
302
+ /// loop runs continuously as long as a callback is registered. `cancel`-ing the
303
+ /// loop = clearing `RAF_CALLBACK`. The `value_call` runs the frame closure to
304
+ /// completion; all WebGPU recording happens synchronously inside.
305
+ fn tick(ts: f64) {
306
+ let cb = RAF_CALLBACK.with(|c| c.borrow().clone());
307
+ // This frame's pending schedule is now consumed.
308
+ RAF_SCHEDULED.with(|f| f.set(false));
309
+ if let Some(cb) = cb {
310
+ if matches!(cb, Value::Function(_)) {
311
+ value_call(&cb, &[Value::Number(ts)]);
312
+ }
313
+ // Re-arm only if the callback is still registered (allows a future
314
+ // cancel by clearing RAF_CALLBACK).
315
+ if RAF_CALLBACK.with(|c| c.borrow().is_some()) {
316
+ schedule_raf();
317
+ }
318
+ }
319
+ }
320
+
321
+ fn schedule_raf() {
322
+ // Coalesce: at most one rAF in flight at a time.
323
+ if RAF_SCHEDULED.with(|f| f.get()) {
324
+ return;
325
+ }
326
+ RAF_SCHEDULED.with(|f| f.set(true));
327
+ RAF_CLOSURE.with(|slot| {
328
+ let mut s = slot.borrow_mut();
329
+ if s.is_none() {
330
+ *s = Some(Closure::wrap(Box::new(|ts: f64| tick(ts)) as Box<dyn FnMut(f64)>));
331
+ }
332
+ let g = js_sys::global();
333
+ if let Ok(raf) = js_sys::Reflect::get(&g, &JsValue::from_str("requestAnimationFrame")) {
334
+ if let Ok(raf_fn) = raf.dyn_into::<js_sys::Function>() {
335
+ let _ = raf_fn.call1(&g, s.as_ref().unwrap().as_ref().unchecked_ref());
336
+ }
337
+ }
338
+ });
339
+ }
340
+
341
+ fn ffi_request_animation_frame() -> Value {
342
+ Value::native(|args: &[Value]| {
343
+ if let Some(cb) = args.first() {
344
+ RAF_CALLBACK.with(|c| *c.borrow_mut() = Some(cb.clone()));
345
+ }
346
+ schedule_raf();
347
+ Value::Null
348
+ })
349
+ }
350
+
351
+ // ---------------------------------------------------------------------------
352
+ // Install + entry point
353
+ // ---------------------------------------------------------------------------
354
+
355
+ fn install_ffi(vm: &mut Vm) {
356
+ vm.set_global("js_global".into(), ffi_js_global());
357
+ vm.set_global("js_get".into(), ffi_js_get());
358
+ vm.set_global("js_set".into(), ffi_js_set());
359
+ vm.set_global("js_call".into(), ffi_js_call());
360
+ vm.set_global("js_new".into(), ffi_js_new());
361
+ vm.set_global("js_typeof".into(), ffi_js_typeof());
362
+ vm.set_global("f32a".into(), ffi_f32a());
363
+ vm.set_global("u16a".into(), ffi_u16a());
364
+ vm.set_global("u8a".into(), ffi_u8a());
365
+ vm.set_global("request_animation_frame".into(), ffi_request_animation_frame());
366
+ }
367
+
368
+ #[wasm_bindgen]
369
+ extern "C" {
370
+ #[wasm_bindgen(js_namespace = console, js_name = error)]
371
+ fn console_error(s: &str);
372
+ }
373
+
374
+ fn set_panic_hook() {
375
+ use std::sync::Once;
376
+ static HOOK: Once = Once::new();
377
+ HOOK.call_once(|| {
378
+ std::panic::set_hook(Box::new(|info| {
379
+ console_error(&format!("tish wasm panic: {}", info));
380
+ }));
381
+ });
382
+ }
383
+
384
+ /// Browser entry: run a tish bytecode `chunk` with the JS-interop FFI installed
385
+ /// and the host environment object (device/queue/context/format/canvas/assets,
386
+ /// built by the page's async startup glue) exposed as the `host` global.
387
+ ///
388
+ /// Returns after top-level tish runs; the `requestAnimationFrame` loop keeps
389
+ /// the captured globals alive via the stored callback, so the VM state persists
390
+ /// across frames even though this call returns.
391
+ /// Invoke the registered frame callback exactly once, without re-scheduling.
392
+ /// For driving frames deterministically from JS when `requestAnimationFrame` is
393
+ /// throttled (e.g. a hidden/offscreen preview tab) — verification & debugging.
394
+ #[wasm_bindgen]
395
+ pub fn tick_once(ts: f64) {
396
+ let cb = RAF_CALLBACK.with(|c| c.borrow().clone());
397
+ if let Some(cb) = cb {
398
+ if matches!(cb, Value::Function(_)) {
399
+ value_call(&cb, &[Value::Number(ts)]);
400
+ }
401
+ }
402
+ }
403
+
404
+ #[wasm_bindgen]
405
+ pub fn start(chunk: Vec<u8>, env: JsValue) -> Result<(), JsValue> {
406
+ set_panic_hook();
407
+ let chunk = deserialize(&chunk).map_err(|e| JsValue::from_str(&e))?;
408
+ let mut vm = Vm::new();
409
+ install_ffi(&mut vm);
410
+ vm.set_global("host".into(), wrap(env));
411
+ vm.run(&chunk).map_err(|e| JsValue::from_str(&e))?;
412
+ Ok(())
413
+ }
@@ -0,0 +1,42 @@
1
+ //! Tish VM as a WebAssembly module.
2
+ //!
3
+ //! Two targets:
4
+ //! - **Browser** (wasm32-unknown-unknown): use `--features browser`, wasm-bindgen, console output
5
+ //! - **WASI/Wasmtime** (wasm32-wasip1): optional `timers` / `http` / … via Cargo features; `compile_to_wasi`
6
+ //! merges CLI capability flags with imports and always enables `timers` when globals use `setTimeout`.
7
+
8
+ use tishlang_bytecode::deserialize;
9
+ use tishlang_vm::Vm;
10
+
11
+ /// Browser WebGPU / JS-interop FFI + requestAnimationFrame render loop.
12
+ /// Adds the `start(chunk, env)` wasm-bindgen entry used by the engine.
13
+ #[cfg(feature = "gpu")]
14
+ pub mod gpu;
15
+
16
+ /// Run serialized Tish bytecode (WASI/Wasmtime or native).
17
+ ///
18
+ /// `chunk` is the output of `tishlang_bytecode::serialize(chunk)`.
19
+ /// Uses println! for output (WASI fd_write when built for wasm32-wasi).
20
+ #[cfg(not(feature = "browser"))]
21
+ pub fn run_wasi(chunk: &[u8]) -> Result<(), String> {
22
+ let chunk = deserialize(chunk)?;
23
+ let mut vm = Vm::new();
24
+ vm.run(&chunk)?;
25
+ Ok(())
26
+ }
27
+
28
+ /// Run serialized Tish bytecode in the browser.
29
+ ///
30
+ /// `chunk` is the output of `tishlang_bytecode::serialize(chunk)`.
31
+ /// Errors are returned as a JsValue (string).
32
+ #[cfg(feature = "browser")]
33
+ use wasm_bindgen::prelude::*;
34
+
35
+ #[cfg(feature = "browser")]
36
+ #[wasm_bindgen]
37
+ pub fn run(chunk: Vec<u8>) -> Result<(), JsValue> {
38
+ let chunk = deserialize(&chunk).map_err(|e| JsValue::from_str(&e))?;
39
+ let mut vm = Vm::new();
40
+ vm.run(&chunk).map_err(|e| JsValue::from_str(&e))?;
41
+ Ok(())
42
+ }
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "tishlang_cargo_bindgen"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Generate Rust glue for Tish cargo: imports (bindgen-style, pre-commit friendly)"
6
+ license-file = { workspace = true }
7
+ repository = { workspace = true }
8
+
9
+ [[bin]]
10
+ name = "tishlang-cargo-bindgen"
11
+ path = "src/main.rs"
12
+
13
+ [lib]
14
+ name = "tishlang_cargo_bindgen"
15
+ path = "src/lib.rs"
16
+
17
+ [dependencies]
18
+ cargo_metadata = "0.19"
19
+ clap = { version = "4.6", features = ["derive", "color"] }
20
+ pathdiff = "0.2"
21
+ proc-macro2 = { version = "1.0", features = ["span-locations"] }
22
+ serde_json = "1"
23
+ syn = { version = "2.0", features = ["full", "extra-traits"] }
24
+ tempfile = "3"
25
+ toml = "0.8"
26
+ walkdir = "2"