@tishlang/tish-format 1.0.12 → 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.
Files changed (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,692 @@
1
+ //! Shared JSX lowering: emit `h(tag, props, children)` as JavaScript or Rust (`Value`) source.
2
+
3
+ use std::collections::HashSet;
4
+
5
+ use tishlang_ast::{
6
+ ArrayElement, ArrowBody, CallArg, ExportDeclaration, Expr, JsxAttrValue, JsxChild, JsxProp,
7
+ Literal, MemberProp, ObjectProp, Program, Statement,
8
+ };
9
+
10
+ /// Escape a Tish identifier for Rust output (matches `tishlang_compile` conventions).
11
+ pub fn escape_ident_rust(s: &str) -> String {
12
+ if s == "await" || s == "default" {
13
+ format!("_{}", s)
14
+ } else {
15
+ s.to_string()
16
+ }
17
+ }
18
+
19
+ /// Emit JSX expression as JavaScript (same rules as legacy `tishlang_compile_js`).
20
+ pub fn emit_jsx_js<F, E>(expr: &Expr, emit_expr: &mut F) -> Result<String, E>
21
+ where
22
+ F: FnMut(&Expr) -> Result<String, E>,
23
+ E: From<String>,
24
+ {
25
+ match expr {
26
+ Expr::JsxElement {
27
+ tag,
28
+ props,
29
+ children,
30
+ ..
31
+ } => {
32
+ let tag_str = if tag
33
+ .chars()
34
+ .next()
35
+ .map(|c| c.is_uppercase())
36
+ .unwrap_or(false)
37
+ {
38
+ tag.as_ref().to_string()
39
+ } else {
40
+ format!("{:?}", tag.as_ref())
41
+ };
42
+ let props_str = emit_jsx_props_js(props, emit_expr)?;
43
+ let children_strs: Result<Vec<_>, _> = children
44
+ .iter()
45
+ .map(|c| emit_jsx_child_js(c, emit_expr))
46
+ .collect();
47
+ let children_str = children_strs?.join(", ");
48
+ Ok(format!("h({}, {}, [{}])", tag_str, props_str, children_str))
49
+ }
50
+ Expr::JsxFragment { children, .. } => {
51
+ let children_strs: Result<Vec<_>, _> = children
52
+ .iter()
53
+ .map(|c| emit_jsx_child_js(c, emit_expr))
54
+ .collect();
55
+ let children_str = children_strs?.join(", ");
56
+ Ok(format!("h(Fragment, null, [{}])", children_str))
57
+ }
58
+ _ => Err(emit_err("emit_jsx_js: not a JSX expression")),
59
+ }
60
+ }
61
+
62
+ fn emit_err<E>(msg: &str) -> E
63
+ where
64
+ E: From<String>,
65
+ {
66
+ E::from(msg.to_string())
67
+ }
68
+
69
+ fn emit_jsx_props_js<F, E>(props: &[JsxProp], emit_expr: &mut F) -> Result<String, E>
70
+ where
71
+ F: FnMut(&Expr) -> Result<String, E>,
72
+ {
73
+ if props.is_empty() {
74
+ return Ok("null".to_string());
75
+ }
76
+ let parts: Result<Vec<_>, _> = props
77
+ .iter()
78
+ .map(|p| match p {
79
+ JsxProp::Attr { name, value } => {
80
+ let val = match value {
81
+ JsxAttrValue::String(s) => format!("{:?}", s.as_ref()),
82
+ JsxAttrValue::Expr(e) => emit_expr(e)?,
83
+ JsxAttrValue::ImplicitTrue => "true".to_string(),
84
+ };
85
+ let key = name.as_ref();
86
+ Ok(if key.chars().all(|c| c.is_alphanumeric() || c == '_') {
87
+ format!("{}: {}", key, val)
88
+ } else {
89
+ format!("{:?}: {}", key, val)
90
+ })
91
+ }
92
+ JsxProp::Spread(e) => Ok(format!("...{}", emit_expr(e)?)),
93
+ })
94
+ .collect();
95
+ Ok(format!("{{ {} }}", parts?.join(", ")))
96
+ }
97
+
98
+ fn emit_jsx_child_js<F, E>(child: &JsxChild, emit_expr: &mut F) -> Result<String, E>
99
+ where
100
+ F: FnMut(&Expr) -> Result<String, E>,
101
+ {
102
+ match child {
103
+ JsxChild::Text(s) => Ok(format!("{:?}", s.as_ref())),
104
+ JsxChild::Expr(e) => {
105
+ let inner = emit_expr(e)?;
106
+ let needs_string = matches!(
107
+ e,
108
+ Expr::Literal {
109
+ value: Literal::Number(_) | Literal::Bool(_) | Literal::Null,
110
+ ..
111
+ }
112
+ );
113
+ Ok(if needs_string {
114
+ format!("String({})", inner)
115
+ } else {
116
+ inner
117
+ })
118
+ }
119
+ }
120
+ }
121
+
122
+ /// Every `fn Foo` name in the program (including nested bodies), for Rust JSX tag lowering.
123
+ ///
124
+ /// PascalCase JSX tags that match a name here are emitted as a Rust identifier (component
125
+ /// `Value::Function`). Other PascalCase tags become `Value::String("Tag")` (native intrinsics).
126
+ pub fn collect_fun_decl_names(program: &Program) -> HashSet<String> {
127
+ let mut names = HashSet::new();
128
+ for s in &program.statements {
129
+ collect_fun_decl_names_stmt(s, &mut names);
130
+ }
131
+ names
132
+ }
133
+
134
+ fn collect_fun_decl_names_stmt(stmt: &Statement, names: &mut HashSet<String>) {
135
+ match stmt {
136
+ Statement::FunDecl { name, body, .. } => {
137
+ names.insert(name.to_string());
138
+ collect_fun_decl_names_stmt(body, names);
139
+ }
140
+ Statement::Block { statements, .. } => {
141
+ for s in statements {
142
+ collect_fun_decl_names_stmt(s, names);
143
+ }
144
+ }
145
+ Statement::Multi { statements, .. } => {
146
+ for s in statements {
147
+ collect_fun_decl_names_stmt(s, names);
148
+ }
149
+ }
150
+ Statement::VarDecl { init, .. } => {
151
+ if let Some(e) = init {
152
+ collect_fun_decl_names_expr(e, names);
153
+ }
154
+ }
155
+ Statement::VarDeclDestructure { init, .. } => collect_fun_decl_names_expr(init, names),
156
+ Statement::ExprStmt { expr, .. } => collect_fun_decl_names_expr(expr, names),
157
+ Statement::If {
158
+ cond,
159
+ then_branch,
160
+ else_branch,
161
+ ..
162
+ } => {
163
+ collect_fun_decl_names_expr(cond, names);
164
+ collect_fun_decl_names_stmt(then_branch, names);
165
+ if let Some(e) = else_branch {
166
+ collect_fun_decl_names_stmt(e, names);
167
+ }
168
+ }
169
+ Statement::While { cond, body, .. } => {
170
+ collect_fun_decl_names_expr(cond, names);
171
+ collect_fun_decl_names_stmt(body, names);
172
+ }
173
+ Statement::For {
174
+ init,
175
+ cond,
176
+ update,
177
+ body,
178
+ ..
179
+ } => {
180
+ if let Some(i) = init {
181
+ collect_fun_decl_names_stmt(i, names);
182
+ }
183
+ if let Some(c) = cond {
184
+ collect_fun_decl_names_expr(c, names);
185
+ }
186
+ if let Some(u) = update {
187
+ collect_fun_decl_names_expr(u, names);
188
+ }
189
+ collect_fun_decl_names_stmt(body, names);
190
+ }
191
+ Statement::ForOf { iterable, body, .. } => {
192
+ collect_fun_decl_names_expr(iterable, names);
193
+ collect_fun_decl_names_stmt(body, names);
194
+ }
195
+ Statement::Return { value, .. } => {
196
+ if let Some(e) = value {
197
+ collect_fun_decl_names_expr(e, names);
198
+ }
199
+ }
200
+ Statement::Switch {
201
+ expr,
202
+ cases,
203
+ default_body,
204
+ ..
205
+ } => {
206
+ collect_fun_decl_names_expr(expr, names);
207
+ for (ce, ss) in cases {
208
+ if let Some(e) = ce {
209
+ collect_fun_decl_names_expr(e, names);
210
+ }
211
+ for s in ss {
212
+ collect_fun_decl_names_stmt(s, names);
213
+ }
214
+ }
215
+ if let Some(ss) = default_body {
216
+ for s in ss {
217
+ collect_fun_decl_names_stmt(s, names);
218
+ }
219
+ }
220
+ }
221
+ Statement::DoWhile { body, cond, .. } => {
222
+ collect_fun_decl_names_stmt(body, names);
223
+ collect_fun_decl_names_expr(cond, names);
224
+ }
225
+ Statement::Throw { value, .. } => collect_fun_decl_names_expr(value, names),
226
+ Statement::Try {
227
+ body,
228
+ catch_body,
229
+ finally_body,
230
+ ..
231
+ } => {
232
+ collect_fun_decl_names_stmt(body, names);
233
+ if let Some(c) = catch_body {
234
+ collect_fun_decl_names_stmt(c, names);
235
+ }
236
+ if let Some(f) = finally_body {
237
+ collect_fun_decl_names_stmt(f, names);
238
+ }
239
+ }
240
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
241
+ ExportDeclaration::Named(inner) => collect_fun_decl_names_stmt(inner, names),
242
+ ExportDeclaration::Default(e) => collect_fun_decl_names_expr(e, names),
243
+ },
244
+ Statement::Import { .. }
245
+ | Statement::Break { .. }
246
+ | Statement::Continue { .. }
247
+ | Statement::TypeAlias { .. }
248
+ | Statement::DeclareVar { .. }
249
+ | Statement::DeclareFun { .. } => {}
250
+ }
251
+ }
252
+
253
+ fn collect_fun_decl_names_expr(expr: &Expr, names: &mut HashSet<String>) {
254
+ match expr {
255
+ Expr::ArrowFunction { body, .. } => match body {
256
+ ArrowBody::Expr(e) => collect_fun_decl_names_expr(e, names),
257
+ ArrowBody::Block(s) => collect_fun_decl_names_stmt(s, names),
258
+ },
259
+ Expr::Binary { left, right, .. } => {
260
+ collect_fun_decl_names_expr(left, names);
261
+ collect_fun_decl_names_expr(right, names);
262
+ }
263
+ Expr::Unary { operand, .. } => collect_fun_decl_names_expr(operand, names),
264
+ Expr::Assign { value, .. } => collect_fun_decl_names_expr(value, names),
265
+ Expr::Call { callee, args, .. } => {
266
+ collect_fun_decl_names_expr(callee, names);
267
+ for a in args {
268
+ match a {
269
+ CallArg::Expr(e) | CallArg::Spread(e) => collect_fun_decl_names_expr(e, names),
270
+ }
271
+ }
272
+ }
273
+ Expr::Member { object, prop, .. } => {
274
+ collect_fun_decl_names_expr(object, names);
275
+ if let MemberProp::Expr(e) = prop {
276
+ collect_fun_decl_names_expr(e, names);
277
+ }
278
+ }
279
+ Expr::Index { object, index, .. } => {
280
+ collect_fun_decl_names_expr(object, names);
281
+ collect_fun_decl_names_expr(index, names);
282
+ }
283
+ Expr::Conditional {
284
+ cond,
285
+ then_branch,
286
+ else_branch,
287
+ ..
288
+ } => {
289
+ collect_fun_decl_names_expr(cond, names);
290
+ collect_fun_decl_names_expr(then_branch, names);
291
+ collect_fun_decl_names_expr(else_branch, names);
292
+ }
293
+ Expr::Array { elements, .. } => {
294
+ for el in elements {
295
+ match el {
296
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => {
297
+ collect_fun_decl_names_expr(e, names);
298
+ }
299
+ }
300
+ }
301
+ }
302
+ Expr::Object { props, .. } => {
303
+ for p in props {
304
+ match p {
305
+ ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => {
306
+ collect_fun_decl_names_expr(e, names);
307
+ }
308
+ }
309
+ }
310
+ }
311
+ Expr::NullishCoalesce { left, right, .. } => {
312
+ collect_fun_decl_names_expr(left, names);
313
+ collect_fun_decl_names_expr(right, names);
314
+ }
315
+ Expr::TemplateLiteral { exprs, .. } => {
316
+ for e in exprs {
317
+ collect_fun_decl_names_expr(e, names);
318
+ }
319
+ }
320
+ Expr::Await { operand, .. } | Expr::TypeOf { operand, .. } => {
321
+ collect_fun_decl_names_expr(operand, names);
322
+ }
323
+ Expr::Delete { target, .. } => {
324
+ collect_fun_decl_names_expr(target, names);
325
+ }
326
+ Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. } => {
327
+ collect_fun_decl_names_expr(value, names);
328
+ }
329
+ Expr::MemberAssign { object, value, .. } => {
330
+ collect_fun_decl_names_expr(object, names);
331
+ collect_fun_decl_names_expr(value, names);
332
+ }
333
+ Expr::IndexAssign {
334
+ object,
335
+ index,
336
+ value,
337
+ ..
338
+ } => {
339
+ collect_fun_decl_names_expr(object, names);
340
+ collect_fun_decl_names_expr(index, names);
341
+ collect_fun_decl_names_expr(value, names);
342
+ }
343
+ Expr::New { callee, args, .. } => {
344
+ collect_fun_decl_names_expr(callee, names);
345
+ for a in args {
346
+ match a {
347
+ CallArg::Expr(e) | CallArg::Spread(e) => collect_fun_decl_names_expr(e, names),
348
+ }
349
+ }
350
+ }
351
+ Expr::PostfixInc { .. }
352
+ | Expr::PrefixInc { .. }
353
+ | Expr::PostfixDec { .. }
354
+ | Expr::PrefixDec { .. } => {}
355
+ Expr::JsxElement {
356
+ props, children, ..
357
+ } => {
358
+ for p in props {
359
+ match p {
360
+ JsxProp::Attr { value, .. } => {
361
+ if let JsxAttrValue::Expr(e) = value {
362
+ collect_fun_decl_names_expr(e, names);
363
+ }
364
+ }
365
+ JsxProp::Spread(e) => collect_fun_decl_names_expr(e, names),
366
+ }
367
+ }
368
+ for c in children {
369
+ if let JsxChild::Expr(e) = c {
370
+ collect_fun_decl_names_expr(e, names);
371
+ }
372
+ }
373
+ }
374
+ Expr::JsxFragment { children, .. } => {
375
+ for c in children {
376
+ if let JsxChild::Expr(e) = c {
377
+ collect_fun_decl_names_expr(e, names);
378
+ }
379
+ }
380
+ }
381
+ Expr::Literal { .. } | Expr::Ident { .. } | Expr::NativeModuleLoad { .. } => {}
382
+ }
383
+ }
384
+
385
+ /// Emit JSX as Rust `Value` by calling `tishlang_ui::ui_h` directly (no closure capture of a local `h` binding).
386
+ pub fn emit_jsx_rust<F, E>(
387
+ expr: &Expr,
388
+ emit_expr: &mut F,
389
+ fun_decls: &HashSet<String>,
390
+ ) -> Result<String, E>
391
+ where
392
+ F: FnMut(&Expr) -> Result<String, E>,
393
+ E: From<String>,
394
+ {
395
+ match expr {
396
+ Expr::JsxElement {
397
+ tag,
398
+ props,
399
+ children,
400
+ ..
401
+ } => {
402
+ let is_component = tag
403
+ .chars()
404
+ .next()
405
+ .map(|c| c.is_uppercase())
406
+ .unwrap_or(false);
407
+ let tag_rust = if is_component {
408
+ if fun_decls.contains(tag.as_ref()) {
409
+ escape_ident_rust(tag.as_ref())
410
+ } else {
411
+ format!("Value::String({:?}.into())", tag.as_ref())
412
+ }
413
+ } else {
414
+ format!("Value::String({:?}.into())", tag.as_ref())
415
+ };
416
+ let props_rust = emit_jsx_props_rust(props, emit_expr)?;
417
+ let child_parts: Result<Vec<_>, _> = children
418
+ .iter()
419
+ .map(|c| emit_jsx_child_rust(c, emit_expr))
420
+ .collect();
421
+ let children_rust = format!(
422
+ "Value::Array(VmRef::new(vec![{}]))",
423
+ child_parts?.join(", ")
424
+ );
425
+ Ok(wrap_h_call_rust(&tag_rust, &props_rust, &children_rust))
426
+ }
427
+ Expr::JsxFragment { children, .. } => {
428
+ let child_parts: Result<Vec<_>, _> = children
429
+ .iter()
430
+ .map(|c| emit_jsx_child_rust(c, emit_expr))
431
+ .collect();
432
+ let children_rust = format!(
433
+ "Value::Array(VmRef::new(vec![{}]))",
434
+ child_parts?.join(", ")
435
+ );
436
+ Ok(wrap_h_call_rust("Fragment", "Value::Null", &children_rust))
437
+ }
438
+ _ => Err(E::from("emit_jsx_rust: not a JSX expression".to_string())),
439
+ }
440
+ }
441
+
442
+ fn wrap_h_call_rust(tag: &str, props: &str, children: &str) -> String {
443
+ format!(
444
+ "tishlang_ui::ui_h(&[({}).clone(), ({}).clone(), ({}).clone()])",
445
+ tag, props, children
446
+ )
447
+ }
448
+
449
+ fn emit_jsx_props_rust<F, E>(props: &[JsxProp], emit_expr: &mut F) -> Result<String, E>
450
+ where
451
+ F: FnMut(&Expr) -> Result<String, E>,
452
+ E: From<String>,
453
+ {
454
+ if props.is_empty() {
455
+ return Ok("Value::Null".to_string());
456
+ }
457
+ let has_spread = props.iter().any(|p| matches!(p, JsxProp::Spread(_)));
458
+ if has_spread {
459
+ let mut parts = Vec::new();
460
+ for prop in props {
461
+ match prop {
462
+ JsxProp::Attr { name, value } => {
463
+ let val = match value {
464
+ JsxAttrValue::String(s) => {
465
+ format!("Value::String({:?}.into())", s.as_ref())
466
+ }
467
+ JsxAttrValue::Expr(e) => emit_expr(e)?,
468
+ JsxAttrValue::ImplicitTrue => "Value::Bool(true)".to_string(),
469
+ };
470
+ parts.push(format!(
471
+ "_obj.insert(Arc::from({:?}), ({}).clone());",
472
+ name.as_ref(),
473
+ val
474
+ ));
475
+ }
476
+ JsxProp::Spread(e) => {
477
+ let val = emit_expr(e)?;
478
+ parts.push(format!(
479
+ "if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().strings.iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}",
480
+ val
481
+ ));
482
+ }
483
+ }
484
+ }
485
+ Ok(format!(
486
+ "{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::object(_obj) }}",
487
+ parts.join(" ")
488
+ ))
489
+ } else {
490
+ let mut kv = Vec::new();
491
+ for prop in props {
492
+ if let JsxProp::Attr { name, value } = prop {
493
+ let val = match value {
494
+ JsxAttrValue::String(s) => {
495
+ format!("Value::String({:?}.into())", s.as_ref())
496
+ }
497
+ JsxAttrValue::Expr(e) => emit_expr(e)?,
498
+ JsxAttrValue::ImplicitTrue => "Value::Bool(true)".to_string(),
499
+ };
500
+ kv.push(format!(
501
+ "(Arc::from({:?}), ({}).clone())",
502
+ name.as_ref(),
503
+ val
504
+ ));
505
+ }
506
+ }
507
+ Ok(format!(
508
+ "Value::object(ObjectMap::from([{}]))",
509
+ kv.join(", ")
510
+ ))
511
+ }
512
+ }
513
+
514
+ fn emit_jsx_child_rust<F, E>(child: &JsxChild, emit_expr: &mut F) -> Result<String, E>
515
+ where
516
+ F: FnMut(&Expr) -> Result<String, E>,
517
+ E: From<String>,
518
+ {
519
+ match child {
520
+ JsxChild::Text(s) => Ok(format!("Value::String({:?}.into())", s.as_ref())),
521
+ JsxChild::Expr(e) => {
522
+ let inner = emit_expr(e)?;
523
+ let needs_string = matches!(
524
+ e,
525
+ Expr::Literal {
526
+ value: Literal::Number(_) | Literal::Bool(_) | Literal::Null,
527
+ ..
528
+ }
529
+ );
530
+ Ok(if needs_string {
531
+ format!("Value::String(({}).to_display_string().into())", inner)
532
+ } else {
533
+ format!("({}).clone()", inner)
534
+ })
535
+ }
536
+ }
537
+ }
538
+
539
+ /// Whether the program contains any JSX syntax (for conditional native UI globals).
540
+ pub fn program_contains_jsx(program: &tishlang_ast::Program) -> bool {
541
+ program.statements.iter().any(stmt_contains_jsx)
542
+ }
543
+
544
+ fn stmt_contains_jsx(stmt: &tishlang_ast::Statement) -> bool {
545
+ use tishlang_ast::{ExportDeclaration, Statement};
546
+ match stmt {
547
+ Statement::Block { statements, .. } => statements.iter().any(stmt_contains_jsx),
548
+ Statement::Multi { statements, .. } => statements.iter().any(stmt_contains_jsx),
549
+ Statement::VarDecl { init, .. } => init.as_ref().is_some_and(expr_contains_jsx),
550
+ Statement::VarDeclDestructure { init, .. } => expr_contains_jsx(init),
551
+ Statement::ExprStmt { expr, .. } => expr_contains_jsx(expr),
552
+ Statement::Return { value, .. } => value.as_ref().is_some_and(expr_contains_jsx),
553
+ Statement::If {
554
+ cond,
555
+ then_branch,
556
+ else_branch,
557
+ ..
558
+ } => {
559
+ expr_contains_jsx(cond)
560
+ || stmt_contains_jsx(then_branch)
561
+ || else_branch.as_ref().is_some_and(|s| stmt_contains_jsx(s))
562
+ }
563
+ Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
564
+ expr_contains_jsx(cond) || stmt_contains_jsx(body)
565
+ }
566
+ Statement::For {
567
+ init,
568
+ cond,
569
+ update,
570
+ body,
571
+ ..
572
+ } => {
573
+ init.as_ref().is_some_and(|s| stmt_contains_jsx(s))
574
+ || cond.as_ref().is_some_and(expr_contains_jsx)
575
+ || update.as_ref().is_some_and(expr_contains_jsx)
576
+ || stmt_contains_jsx(body)
577
+ }
578
+ Statement::ForOf { iterable, body, .. } => {
579
+ expr_contains_jsx(iterable) || stmt_contains_jsx(body)
580
+ }
581
+ Statement::Switch {
582
+ expr,
583
+ cases,
584
+ default_body,
585
+ ..
586
+ } => {
587
+ expr_contains_jsx(expr)
588
+ || cases.iter().any(|(e, ss)| {
589
+ e.as_ref().is_some_and(expr_contains_jsx) || ss.iter().any(stmt_contains_jsx)
590
+ })
591
+ || default_body
592
+ .as_ref()
593
+ .is_some_and(|ss| ss.iter().any(stmt_contains_jsx))
594
+ }
595
+ Statement::Try {
596
+ body,
597
+ catch_body,
598
+ finally_body,
599
+ ..
600
+ } => {
601
+ stmt_contains_jsx(body)
602
+ || catch_body.as_ref().is_some_and(|s| stmt_contains_jsx(s))
603
+ || finally_body.as_ref().is_some_and(|s| stmt_contains_jsx(s))
604
+ }
605
+ Statement::FunDecl { body, .. } => stmt_contains_jsx(body),
606
+ Statement::Throw { value, .. } => expr_contains_jsx(value),
607
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
608
+ ExportDeclaration::Named(inner) => stmt_contains_jsx(inner),
609
+ ExportDeclaration::Default(e) => expr_contains_jsx(e),
610
+ },
611
+ Statement::Import { .. }
612
+ | Statement::Break { .. }
613
+ | Statement::Continue { .. }
614
+ | Statement::TypeAlias { .. }
615
+ | Statement::DeclareVar { .. }
616
+ | Statement::DeclareFun { .. } => false,
617
+ }
618
+ }
619
+
620
+ fn expr_contains_jsx(expr: &Expr) -> bool {
621
+ match expr {
622
+ Expr::JsxElement { .. } | Expr::JsxFragment { .. } => true,
623
+ Expr::Binary { left, right, .. } => expr_contains_jsx(left) || expr_contains_jsx(right),
624
+ Expr::Unary { operand, .. } => expr_contains_jsx(operand),
625
+ Expr::Assign { value, .. } => expr_contains_jsx(value),
626
+ Expr::Call { callee, args, .. } => {
627
+ expr_contains_jsx(callee)
628
+ || args.iter().any(|a| match a {
629
+ tishlang_ast::CallArg::Expr(e) | tishlang_ast::CallArg::Spread(e) => {
630
+ expr_contains_jsx(e)
631
+ }
632
+ })
633
+ }
634
+ Expr::Member { object, prop, .. } => {
635
+ expr_contains_jsx(object)
636
+ || matches!(prop, tishlang_ast::MemberProp::Expr(e) if expr_contains_jsx(e))
637
+ }
638
+ Expr::Index { object, index, .. } => expr_contains_jsx(object) || expr_contains_jsx(index),
639
+ Expr::Conditional {
640
+ cond,
641
+ then_branch,
642
+ else_branch,
643
+ ..
644
+ } => {
645
+ expr_contains_jsx(cond)
646
+ || expr_contains_jsx(then_branch)
647
+ || expr_contains_jsx(else_branch)
648
+ }
649
+ Expr::Array { elements, .. } => elements.iter().any(|el| match el {
650
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_contains_jsx(e),
651
+ }),
652
+ Expr::Object { props, .. } => props.iter().any(|p| match p {
653
+ ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_contains_jsx(e),
654
+ }),
655
+ Expr::ArrowFunction { body, .. } => match body {
656
+ tishlang_ast::ArrowBody::Expr(e) => expr_contains_jsx(e),
657
+ tishlang_ast::ArrowBody::Block(s) => stmt_contains_jsx(s),
658
+ },
659
+ Expr::NullishCoalesce { left, right, .. } => {
660
+ expr_contains_jsx(left) || expr_contains_jsx(right)
661
+ }
662
+ Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_contains_jsx),
663
+ Expr::Await { operand, .. } => expr_contains_jsx(operand),
664
+ Expr::TypeOf { operand, .. } => expr_contains_jsx(operand),
665
+ Expr::Delete { target, .. } => expr_contains_jsx(target),
666
+ Expr::PostfixInc { .. }
667
+ | Expr::PrefixInc { .. }
668
+ | Expr::PostfixDec { .. }
669
+ | Expr::PrefixDec { .. } => false,
670
+ Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. } => {
671
+ expr_contains_jsx(value)
672
+ }
673
+ Expr::MemberAssign { object, value, .. } => {
674
+ expr_contains_jsx(object) || expr_contains_jsx(value)
675
+ }
676
+ Expr::IndexAssign {
677
+ object,
678
+ index,
679
+ value,
680
+ ..
681
+ } => expr_contains_jsx(object) || expr_contains_jsx(index) || expr_contains_jsx(value),
682
+ Expr::New { callee, args, .. } => {
683
+ expr_contains_jsx(callee)
684
+ || args.iter().any(|a| match a {
685
+ tishlang_ast::CallArg::Expr(e) | tishlang_ast::CallArg::Spread(e) => {
686
+ expr_contains_jsx(e)
687
+ }
688
+ })
689
+ }
690
+ Expr::Literal { .. } | Expr::Ident { .. } | Expr::NativeModuleLoad { .. } => false,
691
+ }
692
+ }