@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,682 @@
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::VarDecl { init, .. } => {
146
+ if let Some(e) = init {
147
+ collect_fun_decl_names_expr(e, names);
148
+ }
149
+ }
150
+ Statement::VarDeclDestructure { init, .. } => collect_fun_decl_names_expr(init, names),
151
+ Statement::ExprStmt { expr, .. } => collect_fun_decl_names_expr(expr, names),
152
+ Statement::If {
153
+ cond,
154
+ then_branch,
155
+ else_branch,
156
+ ..
157
+ } => {
158
+ collect_fun_decl_names_expr(cond, names);
159
+ collect_fun_decl_names_stmt(then_branch, names);
160
+ if let Some(e) = else_branch {
161
+ collect_fun_decl_names_stmt(e, names);
162
+ }
163
+ }
164
+ Statement::While { cond, body, .. } => {
165
+ collect_fun_decl_names_expr(cond, names);
166
+ collect_fun_decl_names_stmt(body, names);
167
+ }
168
+ Statement::For {
169
+ init,
170
+ cond,
171
+ update,
172
+ body,
173
+ ..
174
+ } => {
175
+ if let Some(i) = init {
176
+ collect_fun_decl_names_stmt(i, names);
177
+ }
178
+ if let Some(c) = cond {
179
+ collect_fun_decl_names_expr(c, names);
180
+ }
181
+ if let Some(u) = update {
182
+ collect_fun_decl_names_expr(u, names);
183
+ }
184
+ collect_fun_decl_names_stmt(body, names);
185
+ }
186
+ Statement::ForOf { iterable, body, .. } => {
187
+ collect_fun_decl_names_expr(iterable, names);
188
+ collect_fun_decl_names_stmt(body, names);
189
+ }
190
+ Statement::Return { value, .. } => {
191
+ if let Some(e) = value {
192
+ collect_fun_decl_names_expr(e, names);
193
+ }
194
+ }
195
+ Statement::Switch {
196
+ expr,
197
+ cases,
198
+ default_body,
199
+ ..
200
+ } => {
201
+ collect_fun_decl_names_expr(expr, names);
202
+ for (ce, ss) in cases {
203
+ if let Some(e) = ce {
204
+ collect_fun_decl_names_expr(e, names);
205
+ }
206
+ for s in ss {
207
+ collect_fun_decl_names_stmt(s, names);
208
+ }
209
+ }
210
+ if let Some(ss) = default_body {
211
+ for s in ss {
212
+ collect_fun_decl_names_stmt(s, names);
213
+ }
214
+ }
215
+ }
216
+ Statement::DoWhile { body, cond, .. } => {
217
+ collect_fun_decl_names_stmt(body, names);
218
+ collect_fun_decl_names_expr(cond, names);
219
+ }
220
+ Statement::Throw { value, .. } => collect_fun_decl_names_expr(value, names),
221
+ Statement::Try {
222
+ body,
223
+ catch_body,
224
+ finally_body,
225
+ ..
226
+ } => {
227
+ collect_fun_decl_names_stmt(body, names);
228
+ if let Some(c) = catch_body {
229
+ collect_fun_decl_names_stmt(c, names);
230
+ }
231
+ if let Some(f) = finally_body {
232
+ collect_fun_decl_names_stmt(f, names);
233
+ }
234
+ }
235
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
236
+ ExportDeclaration::Named(inner) => collect_fun_decl_names_stmt(inner, names),
237
+ ExportDeclaration::Default(e) => collect_fun_decl_names_expr(e, names),
238
+ },
239
+ Statement::Import { .. }
240
+ | Statement::Break { .. }
241
+ | Statement::Continue { .. }
242
+ | Statement::TypeAlias { .. }
243
+ | Statement::DeclareVar { .. }
244
+ | Statement::DeclareFun { .. } => {}
245
+ }
246
+ }
247
+
248
+ fn collect_fun_decl_names_expr(expr: &Expr, names: &mut HashSet<String>) {
249
+ match expr {
250
+ Expr::ArrowFunction { body, .. } => match body {
251
+ ArrowBody::Expr(e) => collect_fun_decl_names_expr(e, names),
252
+ ArrowBody::Block(s) => collect_fun_decl_names_stmt(s, names),
253
+ },
254
+ Expr::Binary { left, right, .. } => {
255
+ collect_fun_decl_names_expr(left, names);
256
+ collect_fun_decl_names_expr(right, names);
257
+ }
258
+ Expr::Unary { operand, .. } => collect_fun_decl_names_expr(operand, names),
259
+ Expr::Assign { value, .. } => collect_fun_decl_names_expr(value, names),
260
+ Expr::Call { callee, args, .. } => {
261
+ collect_fun_decl_names_expr(callee, names);
262
+ for a in args {
263
+ match a {
264
+ CallArg::Expr(e) | CallArg::Spread(e) => collect_fun_decl_names_expr(e, names),
265
+ }
266
+ }
267
+ }
268
+ Expr::Member { object, prop, .. } => {
269
+ collect_fun_decl_names_expr(object, names);
270
+ if let MemberProp::Expr(e) = prop {
271
+ collect_fun_decl_names_expr(e, names);
272
+ }
273
+ }
274
+ Expr::Index { object, index, .. } => {
275
+ collect_fun_decl_names_expr(object, names);
276
+ collect_fun_decl_names_expr(index, names);
277
+ }
278
+ Expr::Conditional {
279
+ cond,
280
+ then_branch,
281
+ else_branch,
282
+ ..
283
+ } => {
284
+ collect_fun_decl_names_expr(cond, names);
285
+ collect_fun_decl_names_expr(then_branch, names);
286
+ collect_fun_decl_names_expr(else_branch, names);
287
+ }
288
+ Expr::Array { elements, .. } => {
289
+ for el in elements {
290
+ match el {
291
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => {
292
+ collect_fun_decl_names_expr(e, names);
293
+ }
294
+ }
295
+ }
296
+ }
297
+ Expr::Object { props, .. } => {
298
+ for p in props {
299
+ match p {
300
+ ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => {
301
+ collect_fun_decl_names_expr(e, names);
302
+ }
303
+ }
304
+ }
305
+ }
306
+ Expr::NullishCoalesce { left, right, .. } => {
307
+ collect_fun_decl_names_expr(left, names);
308
+ collect_fun_decl_names_expr(right, names);
309
+ }
310
+ Expr::TemplateLiteral { exprs, .. } => {
311
+ for e in exprs {
312
+ collect_fun_decl_names_expr(e, names);
313
+ }
314
+ }
315
+ Expr::Await { operand, .. } | Expr::TypeOf { operand, .. } => {
316
+ collect_fun_decl_names_expr(operand, names);
317
+ }
318
+ Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. } => {
319
+ collect_fun_decl_names_expr(value, names);
320
+ }
321
+ Expr::MemberAssign { object, value, .. } => {
322
+ collect_fun_decl_names_expr(object, names);
323
+ collect_fun_decl_names_expr(value, names);
324
+ }
325
+ Expr::IndexAssign {
326
+ object,
327
+ index,
328
+ value,
329
+ ..
330
+ } => {
331
+ collect_fun_decl_names_expr(object, names);
332
+ collect_fun_decl_names_expr(index, names);
333
+ collect_fun_decl_names_expr(value, names);
334
+ }
335
+ Expr::New { callee, args, .. } => {
336
+ collect_fun_decl_names_expr(callee, names);
337
+ for a in args {
338
+ match a {
339
+ CallArg::Expr(e) | CallArg::Spread(e) => collect_fun_decl_names_expr(e, names),
340
+ }
341
+ }
342
+ }
343
+ Expr::PostfixInc { .. }
344
+ | Expr::PrefixInc { .. }
345
+ | Expr::PostfixDec { .. }
346
+ | Expr::PrefixDec { .. } => {}
347
+ Expr::JsxElement {
348
+ props, children, ..
349
+ } => {
350
+ for p in props {
351
+ match p {
352
+ JsxProp::Attr { value, .. } => {
353
+ if let JsxAttrValue::Expr(e) = value {
354
+ collect_fun_decl_names_expr(e, names);
355
+ }
356
+ }
357
+ JsxProp::Spread(e) => collect_fun_decl_names_expr(e, names),
358
+ }
359
+ }
360
+ for c in children {
361
+ if let JsxChild::Expr(e) = c {
362
+ collect_fun_decl_names_expr(e, names);
363
+ }
364
+ }
365
+ }
366
+ Expr::JsxFragment { children, .. } => {
367
+ for c in children {
368
+ if let JsxChild::Expr(e) = c {
369
+ collect_fun_decl_names_expr(e, names);
370
+ }
371
+ }
372
+ }
373
+ Expr::Literal { .. } | Expr::Ident { .. } | Expr::NativeModuleLoad { .. } => {}
374
+ }
375
+ }
376
+
377
+ /// Emit JSX as Rust `Value` by calling `tishlang_ui::ui_h` directly (no closure capture of a local `h` binding).
378
+ pub fn emit_jsx_rust<F, E>(
379
+ expr: &Expr,
380
+ emit_expr: &mut F,
381
+ fun_decls: &HashSet<String>,
382
+ ) -> Result<String, E>
383
+ where
384
+ F: FnMut(&Expr) -> Result<String, E>,
385
+ E: From<String>,
386
+ {
387
+ match expr {
388
+ Expr::JsxElement {
389
+ tag,
390
+ props,
391
+ children,
392
+ ..
393
+ } => {
394
+ let is_component = tag
395
+ .chars()
396
+ .next()
397
+ .map(|c| c.is_uppercase())
398
+ .unwrap_or(false);
399
+ let tag_rust = if is_component {
400
+ if fun_decls.contains(tag.as_ref()) {
401
+ escape_ident_rust(tag.as_ref())
402
+ } else {
403
+ format!("Value::String({:?}.into())", tag.as_ref())
404
+ }
405
+ } else {
406
+ format!("Value::String({:?}.into())", tag.as_ref())
407
+ };
408
+ let props_rust = emit_jsx_props_rust(props, emit_expr)?;
409
+ let child_parts: Result<Vec<_>, _> = children
410
+ .iter()
411
+ .map(|c| emit_jsx_child_rust(c, emit_expr))
412
+ .collect();
413
+ let children_rust = format!(
414
+ "Value::Array(VmRef::new(vec![{}]))",
415
+ child_parts?.join(", ")
416
+ );
417
+ Ok(wrap_h_call_rust(&tag_rust, &props_rust, &children_rust))
418
+ }
419
+ Expr::JsxFragment { children, .. } => {
420
+ let child_parts: Result<Vec<_>, _> = children
421
+ .iter()
422
+ .map(|c| emit_jsx_child_rust(c, emit_expr))
423
+ .collect();
424
+ let children_rust = format!(
425
+ "Value::Array(VmRef::new(vec![{}]))",
426
+ child_parts?.join(", ")
427
+ );
428
+ Ok(wrap_h_call_rust("Fragment", "Value::Null", &children_rust))
429
+ }
430
+ _ => Err(E::from("emit_jsx_rust: not a JSX expression".to_string())),
431
+ }
432
+ }
433
+
434
+ fn wrap_h_call_rust(tag: &str, props: &str, children: &str) -> String {
435
+ format!(
436
+ "tishlang_ui::ui_h(&[({}).clone(), ({}).clone(), ({}).clone()])",
437
+ tag, props, children
438
+ )
439
+ }
440
+
441
+ fn emit_jsx_props_rust<F, E>(props: &[JsxProp], emit_expr: &mut F) -> Result<String, E>
442
+ where
443
+ F: FnMut(&Expr) -> Result<String, E>,
444
+ E: From<String>,
445
+ {
446
+ if props.is_empty() {
447
+ return Ok("Value::Null".to_string());
448
+ }
449
+ let has_spread = props.iter().any(|p| matches!(p, JsxProp::Spread(_)));
450
+ if has_spread {
451
+ let mut parts = Vec::new();
452
+ for prop in props {
453
+ match prop {
454
+ JsxProp::Attr { name, value } => {
455
+ let val = match value {
456
+ JsxAttrValue::String(s) => {
457
+ format!("Value::String({:?}.into())", s.as_ref())
458
+ }
459
+ JsxAttrValue::Expr(e) => emit_expr(e)?,
460
+ JsxAttrValue::ImplicitTrue => "Value::Bool(true)".to_string(),
461
+ };
462
+ parts.push(format!(
463
+ "_obj.insert(Arc::from({:?}), ({}).clone());",
464
+ name.as_ref(),
465
+ val
466
+ ));
467
+ }
468
+ JsxProp::Spread(e) => {
469
+ let val = emit_expr(e)?;
470
+ parts.push(format!(
471
+ "if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().strings.iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}",
472
+ val
473
+ ));
474
+ }
475
+ }
476
+ }
477
+ Ok(format!(
478
+ "{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::object(_obj) }}",
479
+ parts.join(" ")
480
+ ))
481
+ } else {
482
+ let mut kv = Vec::new();
483
+ for prop in props {
484
+ if let JsxProp::Attr { name, value } = prop {
485
+ let val = match value {
486
+ JsxAttrValue::String(s) => {
487
+ format!("Value::String({:?}.into())", s.as_ref())
488
+ }
489
+ JsxAttrValue::Expr(e) => emit_expr(e)?,
490
+ JsxAttrValue::ImplicitTrue => "Value::Bool(true)".to_string(),
491
+ };
492
+ kv.push(format!(
493
+ "(Arc::from({:?}), ({}).clone())",
494
+ name.as_ref(),
495
+ val
496
+ ));
497
+ }
498
+ }
499
+ Ok(format!(
500
+ "Value::object(ObjectMap::from([{}]))",
501
+ kv.join(", ")
502
+ ))
503
+ }
504
+ }
505
+
506
+ fn emit_jsx_child_rust<F, E>(child: &JsxChild, emit_expr: &mut F) -> Result<String, E>
507
+ where
508
+ F: FnMut(&Expr) -> Result<String, E>,
509
+ E: From<String>,
510
+ {
511
+ match child {
512
+ JsxChild::Text(s) => Ok(format!("Value::String({:?}.into())", s.as_ref())),
513
+ JsxChild::Expr(e) => {
514
+ let inner = emit_expr(e)?;
515
+ let needs_string = matches!(
516
+ e,
517
+ Expr::Literal {
518
+ value: Literal::Number(_) | Literal::Bool(_) | Literal::Null,
519
+ ..
520
+ }
521
+ );
522
+ Ok(if needs_string {
523
+ format!("Value::String(({}).to_display_string().into())", inner)
524
+ } else {
525
+ format!("({}).clone()", inner)
526
+ })
527
+ }
528
+ }
529
+ }
530
+
531
+ /// Whether the program contains any JSX syntax (for conditional native UI globals).
532
+ pub fn program_contains_jsx(program: &tishlang_ast::Program) -> bool {
533
+ program.statements.iter().any(stmt_contains_jsx)
534
+ }
535
+
536
+ fn stmt_contains_jsx(stmt: &tishlang_ast::Statement) -> bool {
537
+ use tishlang_ast::{ExportDeclaration, Statement};
538
+ match stmt {
539
+ Statement::Block { statements, .. } => statements.iter().any(stmt_contains_jsx),
540
+ Statement::VarDecl { init, .. } => init.as_ref().is_some_and(expr_contains_jsx),
541
+ Statement::VarDeclDestructure { init, .. } => expr_contains_jsx(init),
542
+ Statement::ExprStmt { expr, .. } => expr_contains_jsx(expr),
543
+ Statement::Return { value, .. } => value.as_ref().is_some_and(expr_contains_jsx),
544
+ Statement::If {
545
+ cond,
546
+ then_branch,
547
+ else_branch,
548
+ ..
549
+ } => {
550
+ expr_contains_jsx(cond)
551
+ || stmt_contains_jsx(then_branch)
552
+ || else_branch.as_ref().is_some_and(|s| stmt_contains_jsx(s))
553
+ }
554
+ Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
555
+ expr_contains_jsx(cond) || stmt_contains_jsx(body)
556
+ }
557
+ Statement::For {
558
+ init,
559
+ cond,
560
+ update,
561
+ body,
562
+ ..
563
+ } => {
564
+ init.as_ref().is_some_and(|s| stmt_contains_jsx(s))
565
+ || cond.as_ref().is_some_and(expr_contains_jsx)
566
+ || update.as_ref().is_some_and(expr_contains_jsx)
567
+ || stmt_contains_jsx(body)
568
+ }
569
+ Statement::ForOf { iterable, body, .. } => {
570
+ expr_contains_jsx(iterable) || stmt_contains_jsx(body)
571
+ }
572
+ Statement::Switch {
573
+ expr,
574
+ cases,
575
+ default_body,
576
+ ..
577
+ } => {
578
+ expr_contains_jsx(expr)
579
+ || cases.iter().any(|(e, ss)| {
580
+ e.as_ref().is_some_and(expr_contains_jsx) || ss.iter().any(stmt_contains_jsx)
581
+ })
582
+ || default_body
583
+ .as_ref()
584
+ .is_some_and(|ss| ss.iter().any(stmt_contains_jsx))
585
+ }
586
+ Statement::Try {
587
+ body,
588
+ catch_body,
589
+ finally_body,
590
+ ..
591
+ } => {
592
+ stmt_contains_jsx(body)
593
+ || catch_body.as_ref().is_some_and(|s| stmt_contains_jsx(s))
594
+ || finally_body.as_ref().is_some_and(|s| stmt_contains_jsx(s))
595
+ }
596
+ Statement::FunDecl { body, .. } => stmt_contains_jsx(body),
597
+ Statement::Throw { value, .. } => expr_contains_jsx(value),
598
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
599
+ ExportDeclaration::Named(inner) => stmt_contains_jsx(inner),
600
+ ExportDeclaration::Default(e) => expr_contains_jsx(e),
601
+ },
602
+ Statement::Import { .. }
603
+ | Statement::Break { .. }
604
+ | Statement::Continue { .. }
605
+ | Statement::TypeAlias { .. }
606
+ | Statement::DeclareVar { .. }
607
+ | Statement::DeclareFun { .. } => false,
608
+ }
609
+ }
610
+
611
+ fn expr_contains_jsx(expr: &Expr) -> bool {
612
+ match expr {
613
+ Expr::JsxElement { .. } | Expr::JsxFragment { .. } => true,
614
+ Expr::Binary { left, right, .. } => expr_contains_jsx(left) || expr_contains_jsx(right),
615
+ Expr::Unary { operand, .. } => expr_contains_jsx(operand),
616
+ Expr::Assign { value, .. } => expr_contains_jsx(value),
617
+ Expr::Call { callee, args, .. } => {
618
+ expr_contains_jsx(callee)
619
+ || args.iter().any(|a| match a {
620
+ tishlang_ast::CallArg::Expr(e) | tishlang_ast::CallArg::Spread(e) => {
621
+ expr_contains_jsx(e)
622
+ }
623
+ })
624
+ }
625
+ Expr::Member { object, prop, .. } => {
626
+ expr_contains_jsx(object)
627
+ || matches!(prop, tishlang_ast::MemberProp::Expr(e) if expr_contains_jsx(e))
628
+ }
629
+ Expr::Index { object, index, .. } => expr_contains_jsx(object) || expr_contains_jsx(index),
630
+ Expr::Conditional {
631
+ cond,
632
+ then_branch,
633
+ else_branch,
634
+ ..
635
+ } => {
636
+ expr_contains_jsx(cond)
637
+ || expr_contains_jsx(then_branch)
638
+ || expr_contains_jsx(else_branch)
639
+ }
640
+ Expr::Array { elements, .. } => elements.iter().any(|el| match el {
641
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_contains_jsx(e),
642
+ }),
643
+ Expr::Object { props, .. } => props.iter().any(|p| match p {
644
+ ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_contains_jsx(e),
645
+ }),
646
+ Expr::ArrowFunction { body, .. } => match body {
647
+ tishlang_ast::ArrowBody::Expr(e) => expr_contains_jsx(e),
648
+ tishlang_ast::ArrowBody::Block(s) => stmt_contains_jsx(s),
649
+ },
650
+ Expr::NullishCoalesce { left, right, .. } => {
651
+ expr_contains_jsx(left) || expr_contains_jsx(right)
652
+ }
653
+ Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_contains_jsx),
654
+ Expr::Await { operand, .. } => expr_contains_jsx(operand),
655
+ Expr::TypeOf { operand, .. } => expr_contains_jsx(operand),
656
+ Expr::PostfixInc { .. }
657
+ | Expr::PrefixInc { .. }
658
+ | Expr::PostfixDec { .. }
659
+ | Expr::PrefixDec { .. } => false,
660
+ Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. } => {
661
+ expr_contains_jsx(value)
662
+ }
663
+ Expr::MemberAssign { object, value, .. } => {
664
+ expr_contains_jsx(object) || expr_contains_jsx(value)
665
+ }
666
+ Expr::IndexAssign {
667
+ object,
668
+ index,
669
+ value,
670
+ ..
671
+ } => expr_contains_jsx(object) || expr_contains_jsx(index) || expr_contains_jsx(value),
672
+ Expr::New { callee, args, .. } => {
673
+ expr_contains_jsx(callee)
674
+ || args.iter().any(|a| match a {
675
+ tishlang_ast::CallArg::Expr(e) | tishlang_ast::CallArg::Spread(e) => {
676
+ expr_contains_jsx(e)
677
+ }
678
+ })
679
+ }
680
+ Expr::Literal { .. } | Expr::Ident { .. } | Expr::NativeModuleLoad { .. } => false,
681
+ }
682
+ }
@@ -0,0 +1,20 @@
1
+ //! JSX lowering (compiler) and UI runtime (vnode + hooks + host) for cross-target Tish UI.
2
+ //!
3
+ //! - Feature **`compiler`**: AST → JS / Rust `h(...)` emission helpers (depends on `tishlang_ast`).
4
+ //! - Feature **`runtime`**: `Value`-based `h`, `Fragment`, hooks, and [`Host`] (depends on `tishlang_core`).
5
+
6
+ #[cfg(feature = "compiler")]
7
+ pub mod jsx;
8
+
9
+ #[cfg(feature = "runtime")]
10
+ pub mod runtime;
11
+
12
+ #[cfg(feature = "runtime")]
13
+ pub use runtime::{
14
+ alloc_root_id, current_root_id, drop_host_for_root, fragment_value, install_host_for_root,
15
+ install_thread_local_host, native_create_root,
16
+ native_use_effect, native_use_layout_effect, native_use_memo, native_use_ref,
17
+ native_use_state, run_with_current_root, ui_h, ui_text,
18
+ unregister_root, unregister_root_hooks_and_effects, with_host_for_root,
19
+ with_thread_local_host, HeadlessHost, Host, RootId, FRAGMENT_SENTINEL, LEGACY_ROOT_ID,
20
+ };