@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,871 @@
1
+ //! Code generation: AST -> JavaScript source.
2
+
3
+ use std::path::{Path, PathBuf};
4
+
5
+ use sourcemap::SourceMapBuilder;
6
+ use tishlang_ast::{
7
+ ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
8
+ FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Statement, UnaryOp,
9
+ };
10
+
11
+ use crate::error::CompileError;
12
+
13
+ struct Codegen {
14
+ output: String,
15
+ indent: usize,
16
+ in_async: bool,
17
+ }
18
+
19
+ fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
20
+ matches!(
21
+ stmt,
22
+ Some(Statement::Break { .. })
23
+ | Some(Statement::Return { .. })
24
+ | Some(Statement::Throw { .. })
25
+ )
26
+ }
27
+
28
+ impl Codegen {
29
+ fn new() -> Self {
30
+ Self {
31
+ output: String::new(),
32
+ indent: 0,
33
+ in_async: false,
34
+ }
35
+ }
36
+
37
+ /// ECMAScript does not allow `if (c) const x = 1` / `while (c) let y = 2` without a block.
38
+ fn stmt_needs_braces_in_js_control_head(stmt: &Statement) -> bool {
39
+ matches!(
40
+ stmt,
41
+ Statement::VarDecl { .. } | Statement::VarDeclDestructure { .. }
42
+ )
43
+ }
44
+
45
+ fn emit_js_control_body(&mut self, body: &Statement) -> Result<(), CompileError> {
46
+ if Self::stmt_needs_braces_in_js_control_head(body) {
47
+ self.writeln("{");
48
+ self.indent += 1;
49
+ self.emit_statement(body)?;
50
+ self.indent -= 1;
51
+ self.writeln("}");
52
+ } else {
53
+ self.indent += 1;
54
+ self.emit_statement(body)?;
55
+ self.indent -= 1;
56
+ }
57
+ Ok(())
58
+ }
59
+
60
+ fn indent_str(&self) -> String {
61
+ " ".repeat(self.indent)
62
+ }
63
+
64
+ fn write(&mut self, s: &str) {
65
+ self.output.push_str(s);
66
+ }
67
+
68
+ fn writeln(&mut self, s: &str) {
69
+ self.output.push_str(&self.indent_str());
70
+ self.output.push_str(s);
71
+ self.output.push('\n');
72
+ }
73
+
74
+ fn output_line(&self) -> u32 {
75
+ self.output
76
+ .as_bytes()
77
+ .iter()
78
+ .filter(|&&b| b == b'\n')
79
+ .count() as u32
80
+ }
81
+
82
+ fn escape_ident(s: &str) -> String {
83
+ let s = s.to_string();
84
+ if s == "await" || s == "default" {
85
+ format!("_{}", s)
86
+ } else {
87
+ s
88
+ }
89
+ }
90
+
91
+ fn emit_program(
92
+ &mut self,
93
+ program: &Program,
94
+ map_sources: Option<(&[PathBuf], &Path)>,
95
+ map_builder: Option<&mut SourceMapBuilder>,
96
+ ) -> Result<(), CompileError> {
97
+ self.write("// Generated by tishlang_compile_js\n");
98
+ match (map_sources, map_builder) {
99
+ (Some((srcs, root)), Some(sm)) => {
100
+ for (i, stmt) in program.statements.iter().enumerate() {
101
+ if i < srcs.len() {
102
+ let dst_line = self.output_line();
103
+ let sp = stmt.span();
104
+ let src_line = sp.start.0.saturating_sub(1) as u32;
105
+ let src_col = sp.start.1.saturating_sub(1) as u32;
106
+ let abs = srcs[i].as_path();
107
+ let root_canon = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
108
+ let abs_canon = abs.canonicalize().unwrap_or_else(|_| abs.to_path_buf());
109
+ let rel = abs_canon
110
+ .strip_prefix(&root_canon)
111
+ .unwrap_or(abs_canon.as_path());
112
+ let rel_str = rel.to_string_lossy();
113
+ sm.add(
114
+ dst_line,
115
+ 0,
116
+ src_line,
117
+ src_col,
118
+ Some(rel_str.as_ref()),
119
+ None,
120
+ false,
121
+ );
122
+ }
123
+ self.emit_statement(stmt)?;
124
+ }
125
+ }
126
+ _ => {
127
+ for stmt in &program.statements {
128
+ self.emit_statement(stmt)?;
129
+ }
130
+ }
131
+ }
132
+ Ok(())
133
+ }
134
+
135
+ fn emit_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
136
+ match stmt {
137
+ Statement::Block { statements, .. } => {
138
+ self.writeln("{");
139
+ self.indent += 1;
140
+ for s in statements {
141
+ self.emit_statement(s)?;
142
+ }
143
+ self.indent -= 1;
144
+ self.writeln("}");
145
+ }
146
+ Statement::VarDecl {
147
+ name,
148
+ mutable,
149
+ type_ann: _,
150
+ init,
151
+ ..
152
+ } => {
153
+ let decl = if *mutable { "let" } else { "const" };
154
+ let escaped = Self::escape_ident(name.as_ref());
155
+ if let Some(expr) = init {
156
+ let e = self.emit_expr(expr)?;
157
+ self.writeln(&format!("{} {} = {};", decl, escaped, e));
158
+ } else {
159
+ self.writeln(&format!("{} {};", decl, escaped));
160
+ }
161
+ }
162
+ Statement::VarDeclDestructure {
163
+ pattern,
164
+ mutable,
165
+ init,
166
+ ..
167
+ } => {
168
+ let decl = if *mutable { "let" } else { "const" };
169
+ let rhs = self.emit_expr(init)?;
170
+ let pat = self.emit_destruct_pattern(pattern)?;
171
+ self.writeln(&format!("{} {} = {};", decl, pat, rhs));
172
+ }
173
+ Statement::ExprStmt { expr, .. } => {
174
+ let e = self.emit_expr(expr)?;
175
+ self.writeln(&format!("{};", e));
176
+ }
177
+ Statement::If {
178
+ cond,
179
+ then_branch,
180
+ else_branch,
181
+ ..
182
+ } => {
183
+ let c = self.emit_expr(cond)?;
184
+ self.writeln(&format!("if ({})", c));
185
+ self.emit_js_control_body(then_branch)?;
186
+ if let Some(eb) = else_branch {
187
+ self.writeln("else");
188
+ self.emit_js_control_body(eb)?;
189
+ }
190
+ }
191
+ Statement::While { cond, body, .. } => {
192
+ let c = self.emit_expr(cond)?;
193
+ self.writeln(&format!("while ({})", c));
194
+ self.emit_js_control_body(body)?;
195
+ }
196
+ Statement::For {
197
+ init,
198
+ cond,
199
+ update,
200
+ body,
201
+ ..
202
+ } => {
203
+ // Keep the whole `for (...)` on one line with normal statement indentation (do not
204
+ // mix bare `write("for (")` with `writeln(")")`, which indents `)` on a new line).
205
+ let mut header = self.indent_str();
206
+ header.push_str("for (");
207
+ if let Some(i) = init {
208
+ match i.as_ref() {
209
+ Statement::VarDecl {
210
+ name,
211
+ mutable,
212
+ init: opt_init,
213
+ ..
214
+ } => {
215
+ let decl = if *mutable { "let" } else { "const" };
216
+ let escaped = Self::escape_ident(name.as_ref());
217
+ if let Some(e) = opt_init {
218
+ let ex = self.emit_expr(e)?;
219
+ header.push_str(&format!("{} {} = {}", decl, escaped, ex));
220
+ } else {
221
+ header.push_str(&format!("{} {}", decl, escaped));
222
+ }
223
+ }
224
+ Statement::ExprStmt { expr, .. } => {
225
+ let ex = self.emit_expr(expr)?;
226
+ header.push_str(&ex);
227
+ }
228
+ _ => return Err(CompileError::new("Unsupported for init")),
229
+ }
230
+ }
231
+ header.push_str("; ");
232
+ if let Some(c) = cond {
233
+ let ce = self.emit_expr(c)?;
234
+ header.push_str(&ce);
235
+ }
236
+ header.push_str("; ");
237
+ if let Some(u) = update {
238
+ let ue = self.emit_expr(u)?;
239
+ header.push_str(&ue);
240
+ }
241
+ header.push(')');
242
+ header.push('\n');
243
+ self.output.push_str(&header);
244
+ self.emit_js_control_body(body)?;
245
+ }
246
+ Statement::ForOf {
247
+ name,
248
+ iterable,
249
+ body,
250
+ ..
251
+ } => {
252
+ let escaped = Self::escape_ident(name.as_ref());
253
+ let it = self.emit_expr(iterable)?;
254
+ self.writeln(&format!("for (const {} of {})", escaped, it));
255
+ self.emit_js_control_body(body)?;
256
+ }
257
+ Statement::Return { value, .. } => {
258
+ if let Some(v) = value {
259
+ let e = self.emit_expr(v)?;
260
+ self.writeln(&format!("return {};", e));
261
+ } else {
262
+ self.writeln("return;");
263
+ }
264
+ }
265
+ Statement::Break { .. } => self.writeln("break;"),
266
+ Statement::Continue { .. } => self.writeln("continue;"),
267
+ Statement::FunDecl {
268
+ async_,
269
+ name,
270
+ params,
271
+ rest_param,
272
+ return_type: _,
273
+ body,
274
+ ..
275
+ } => {
276
+ let async_prefix = if *async_ { "async " } else { "" };
277
+ let escaped = Self::escape_ident(name.as_ref());
278
+ let params_str = self.emit_params(params, rest_param.as_ref())?;
279
+ self.writeln(&format!(
280
+ "{}function {} ({}) {{",
281
+ async_prefix, escaped, params_str
282
+ ));
283
+ self.indent += 1;
284
+ if *async_ {
285
+ self.in_async = true;
286
+ }
287
+ self.emit_statement(body)?;
288
+ if *async_ {
289
+ self.in_async = false;
290
+ }
291
+ self.indent -= 1;
292
+ self.writeln("}");
293
+ }
294
+ Statement::Switch {
295
+ expr,
296
+ cases,
297
+ default_body,
298
+ ..
299
+ } => {
300
+ let e = self.emit_expr(expr)?;
301
+ self.writeln(&format!("switch ({}) {{", e));
302
+ self.indent += 1;
303
+ for (case_expr, stmts) in cases {
304
+ if let Some(ce) = case_expr {
305
+ let c = self.emit_expr(ce)?;
306
+ self.writeln(&format!("case {}:", c));
307
+ }
308
+ for s in stmts {
309
+ self.emit_statement(s)?;
310
+ }
311
+ // Tish has no fall-through; add break unless case ends with break/return/throw
312
+ if !stmt_terminates_switch(stmts.last()) {
313
+ self.writeln("break;");
314
+ }
315
+ }
316
+ if let Some(stmts) = default_body {
317
+ self.writeln("default:");
318
+ for s in stmts {
319
+ self.emit_statement(s)?;
320
+ }
321
+ if !stmt_terminates_switch(stmts.last()) {
322
+ self.writeln("break;");
323
+ }
324
+ }
325
+ self.indent -= 1;
326
+ self.writeln("}");
327
+ }
328
+ Statement::DoWhile { body, cond, .. } => {
329
+ self.writeln("do {");
330
+ self.indent += 1;
331
+ self.emit_statement(body)?;
332
+ self.indent -= 1;
333
+ let c = self.emit_expr(cond)?;
334
+ self.writeln(&format!("}} while ({});", c));
335
+ }
336
+ Statement::Throw { value, .. } => {
337
+ let v = self.emit_expr(value)?;
338
+ self.writeln(&format!("throw {};", v));
339
+ }
340
+ Statement::Try {
341
+ body,
342
+ catch_param,
343
+ catch_body,
344
+ finally_body,
345
+ ..
346
+ } => {
347
+ self.writeln("try {");
348
+ self.indent += 1;
349
+ self.emit_statement(body)?;
350
+ self.indent -= 1;
351
+ if let (Some(param), Some(cb)) = (catch_param, catch_body) {
352
+ let p = Self::escape_ident(param.as_ref());
353
+ self.writeln(&format!("}} catch ({}) {{", p));
354
+ self.indent += 1;
355
+ self.emit_statement(cb)?;
356
+ self.indent -= 1;
357
+ }
358
+ if let Some(fb) = finally_body {
359
+ self.writeln("} finally {");
360
+ self.indent += 1;
361
+ self.emit_statement(fb)?;
362
+ self.indent -= 1;
363
+ }
364
+ self.writeln("}");
365
+ }
366
+ Statement::Import { .. } | Statement::Export { .. } => {
367
+ // Resolved away by merge_modules
368
+ }
369
+ Statement::TypeAlias { .. }
370
+ | Statement::DeclareVar { .. }
371
+ | Statement::DeclareFun { .. } => {}
372
+ }
373
+ Ok(())
374
+ }
375
+
376
+ fn emit_params(
377
+ &mut self,
378
+ params: &[FunParam],
379
+ rest_param: Option<&tishlang_ast::TypedParam>,
380
+ ) -> Result<String, CompileError> {
381
+ let mut parts: Vec<String> = Vec::new();
382
+ for p in params {
383
+ match p {
384
+ FunParam::Simple(tp) => {
385
+ let n = Self::escape_ident(tp.name.as_ref());
386
+ let s = if let Some(ref d) = tp.default {
387
+ format!("{} = {}", n, self.emit_expr(d)?)
388
+ } else {
389
+ n
390
+ };
391
+ parts.push(s);
392
+ }
393
+ FunParam::Destructure {
394
+ pattern,
395
+ type_ann: _,
396
+ default,
397
+ } => {
398
+ let mut s = self.emit_destruct_pattern(pattern)?;
399
+ if let Some(ref d) = default {
400
+ s = format!("{} = {}", s, self.emit_expr(d)?);
401
+ }
402
+ parts.push(s);
403
+ }
404
+ }
405
+ }
406
+ if let Some(rest) = rest_param {
407
+ parts.push(format!("...{}", Self::escape_ident(rest.name.as_ref())));
408
+ }
409
+ Ok(parts.join(", "))
410
+ }
411
+
412
+ fn emit_destruct_pattern(&mut self, pattern: &DestructPattern) -> Result<String, CompileError> {
413
+ match pattern {
414
+ DestructPattern::Array(elements) => {
415
+ let parts: Vec<String> = elements
416
+ .iter()
417
+ .map(|el| match el {
418
+ Some(DestructElement::Ident(n, _)) => Ok(Self::escape_ident(n.as_ref())),
419
+ Some(DestructElement::Pattern(p)) => self.emit_destruct_pattern(p),
420
+ Some(DestructElement::Rest(n, _)) => {
421
+ Ok(format!("...{}", Self::escape_ident(n.as_ref())))
422
+ }
423
+ None => Ok("".to_string()),
424
+ })
425
+ .collect::<Result<_, _>>()?;
426
+ Ok(format!("[{}]", parts.join(", ")))
427
+ }
428
+ DestructPattern::Object(props) => {
429
+ let parts: Vec<String> = props
430
+ .iter()
431
+ .map(|p| {
432
+ let k = p.key.as_ref();
433
+ match &p.value {
434
+ DestructElement::Ident(n, _) => {
435
+ if k == n.as_ref() {
436
+ Ok(k.to_string())
437
+ } else {
438
+ Ok(format!("{}: {}", k, Self::escape_ident(n.as_ref())))
439
+ }
440
+ }
441
+ DestructElement::Pattern(pat) => {
442
+ Ok(format!("{}: {}", k, self.emit_destruct_pattern(pat)?))
443
+ }
444
+ DestructElement::Rest(n, _) => {
445
+ Ok(format!("...{}", Self::escape_ident(n.as_ref())))
446
+ }
447
+ }
448
+ })
449
+ .collect::<Result<_, _>>()?;
450
+ Ok(format!("{{ {} }}", parts.join(", ")))
451
+ }
452
+ }
453
+ }
454
+
455
+ fn emit_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
456
+ Ok(match expr {
457
+ Expr::Literal { value, .. } => match value {
458
+ Literal::Number(n) => format!("{}", n),
459
+ Literal::String(s) => format!("{:?}", s.as_ref()),
460
+ Literal::Bool(b) => format!("{}", b),
461
+ Literal::Null => "null".to_string(),
462
+ },
463
+ Expr::Ident { name, .. } => Self::escape_ident(name.as_ref()),
464
+ Expr::Binary {
465
+ left, op, right, ..
466
+ } => {
467
+ let l = self.emit_expr(left)?;
468
+ let r = self.emit_expr(right)?;
469
+ let op_str = match op {
470
+ BinOp::Add => "+",
471
+ BinOp::Sub => "-",
472
+ BinOp::Mul => "*",
473
+ BinOp::Div => "/",
474
+ BinOp::Mod => "%",
475
+ BinOp::Pow => "**",
476
+ BinOp::Eq => "==",
477
+ BinOp::Ne => "!=",
478
+ BinOp::StrictEq => "===",
479
+ BinOp::StrictNe => "!==",
480
+ BinOp::Lt => "<",
481
+ BinOp::Le => "<=",
482
+ BinOp::Gt => ">",
483
+ BinOp::Ge => ">=",
484
+ BinOp::And => "&&",
485
+ BinOp::Or => "||",
486
+ BinOp::BitAnd => "&",
487
+ BinOp::BitOr => "|",
488
+ BinOp::BitXor => "^",
489
+ BinOp::Shl => "<<",
490
+ BinOp::Shr => ">>",
491
+ BinOp::In => {
492
+ // key in object (property/index existence check)
493
+ return Ok(format!("({} in {})", l, r));
494
+ }
495
+ };
496
+ format!("({} {} {})", l, op_str, r)
497
+ }
498
+ Expr::Unary { op, operand, .. } => {
499
+ let o = self.emit_expr(operand)?;
500
+ match op {
501
+ UnaryOp::Not => format!("!{}", o),
502
+ UnaryOp::Neg => format!("(-{})", o),
503
+ UnaryOp::Pos => format!("(+{})", o),
504
+ UnaryOp::BitNot => format!("(~{})", o),
505
+ UnaryOp::Void => format!("((void {}), null)", o), // Tish void returns null, not undefined
506
+ }
507
+ }
508
+ Expr::Call { callee, args, .. } => {
509
+ let c = self.emit_expr(callee)?;
510
+ let arg_strs: Result<Vec<_>, _> =
511
+ args.iter().map(|a| self.emit_call_arg(a)).collect();
512
+ let arg_strs = arg_strs?.join(", ");
513
+ // Tish uses null for undefined (e.g. empty array pop/shift)
514
+ format!("({}({}) ?? null)", c, arg_strs)
515
+ }
516
+ Expr::New { callee, args, .. } => {
517
+ let c = self.emit_expr(callee)?;
518
+ let arg_strs: Result<Vec<_>, _> =
519
+ args.iter().map(|a| self.emit_call_arg(a)).collect();
520
+ let arg_strs = arg_strs?.join(", ");
521
+ format!("(new {}({}) ?? null)", c, arg_strs)
522
+ }
523
+ Expr::Member {
524
+ object,
525
+ prop,
526
+ optional,
527
+ ..
528
+ } => {
529
+ let obj = self.emit_expr(object)?;
530
+ let expr = match prop {
531
+ MemberProp::Name { name, .. } => {
532
+ if name.parse::<u32>().is_ok()
533
+ || !name.chars().all(|c| c.is_alphanumeric() || c == '_')
534
+ {
535
+ format!("{}[{:?}]", obj, name.as_ref())
536
+ } else {
537
+ let sep = if *optional { "?." } else { "." };
538
+ format!("{}{}{}", obj, sep, name.as_ref())
539
+ }
540
+ }
541
+ MemberProp::Expr(e) => {
542
+ let idx = self.emit_expr(e)?;
543
+ format!("{}[{}]", obj, idx)
544
+ }
545
+ };
546
+ // Tish uses null where JS uses undefined for optional chaining short-circuit only
547
+ if *optional {
548
+ format!("({} ?? null)", expr)
549
+ } else {
550
+ expr
551
+ }
552
+ }
553
+ Expr::Index {
554
+ object,
555
+ index,
556
+ optional,
557
+ ..
558
+ } => {
559
+ let obj = self.emit_expr(object)?;
560
+ let idx = self.emit_expr(index)?;
561
+ let sep = if *optional { "?." } else { "" };
562
+ let expr = format!("{}{}[{}]", obj, sep, idx);
563
+ // Tish uses null for array holes / missing indices (JS returns undefined)
564
+ format!("({} ?? null)", expr)
565
+ }
566
+ Expr::Conditional {
567
+ cond,
568
+ then_branch,
569
+ else_branch,
570
+ ..
571
+ } => {
572
+ let c = self.emit_expr(cond)?;
573
+ let t = self.emit_expr(then_branch)?;
574
+ let e = self.emit_expr(else_branch)?;
575
+ format!("({} ? {} : {})", c, t, e)
576
+ }
577
+ Expr::NullishCoalesce { left, right, .. } => {
578
+ let l = self.emit_expr(left)?;
579
+ let r = self.emit_expr(right)?;
580
+ format!("({} ?? {})", l, r)
581
+ }
582
+ Expr::Array { elements, .. } => {
583
+ let parts: Result<Vec<_>, _> = elements
584
+ .iter()
585
+ .map(|el| match el {
586
+ ArrayElement::Expr(e) => self.emit_expr(e),
587
+ ArrayElement::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
588
+ })
589
+ .collect();
590
+ format!("[{}]", parts?.join(", "))
591
+ }
592
+ Expr::Object { props, .. } => {
593
+ let parts: Result<Vec<_>, _> = props
594
+ .iter()
595
+ .map(|p| match p {
596
+ ObjectProp::KeyValue(k, v) => {
597
+ let key = k.as_ref();
598
+ let val = self.emit_expr(v)?;
599
+ Ok(if key.chars().all(|c| c.is_alphanumeric() || c == '_') {
600
+ format!("{}: {}", key, val)
601
+ } else {
602
+ format!("{:?}: {}", key, val)
603
+ })
604
+ }
605
+ ObjectProp::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
606
+ })
607
+ .collect();
608
+ format!("{{ {} }}", parts?.join(", "))
609
+ }
610
+ Expr::Assign { name, value, .. } => {
611
+ let n = Self::escape_ident(name.as_ref());
612
+ let v = self.emit_expr(value)?;
613
+ format!("({} = {})", n, v)
614
+ }
615
+ Expr::TypeOf { operand, .. } => {
616
+ let o = self.emit_expr(operand)?;
617
+ format!("(typeof {})", o)
618
+ }
619
+ Expr::PostfixInc { name, .. } => {
620
+ format!("{}++", Self::escape_ident(name.as_ref()))
621
+ }
622
+ Expr::PostfixDec { name, .. } => {
623
+ format!("{}--", Self::escape_ident(name.as_ref()))
624
+ }
625
+ Expr::PrefixInc { name, .. } => {
626
+ format!("++{}", Self::escape_ident(name.as_ref()))
627
+ }
628
+ Expr::PrefixDec { name, .. } => {
629
+ format!("--{}", Self::escape_ident(name.as_ref()))
630
+ }
631
+ Expr::CompoundAssign {
632
+ name, op, value, ..
633
+ } => {
634
+ let n = Self::escape_ident(name.as_ref());
635
+ let v = self.emit_expr(value)?;
636
+ let op_str = match op {
637
+ CompoundOp::Add => "+=",
638
+ CompoundOp::Sub => "-=",
639
+ CompoundOp::Mul => "*=",
640
+ CompoundOp::Div => "/=",
641
+ CompoundOp::Mod => "%=",
642
+ };
643
+ format!("({} {} {})", n, op_str, v)
644
+ }
645
+ Expr::LogicalAssign {
646
+ name, op, value, ..
647
+ } => {
648
+ let n = Self::escape_ident(name.as_ref());
649
+ let v = self.emit_expr(value)?;
650
+ let op_str = match op {
651
+ LogicalAssignOp::AndAnd => "&&=",
652
+ LogicalAssignOp::OrOr => "||=",
653
+ LogicalAssignOp::Nullish => "??=",
654
+ };
655
+ format!("({} {} {})", n, op_str, v)
656
+ }
657
+ Expr::MemberAssign {
658
+ object,
659
+ prop,
660
+ value,
661
+ ..
662
+ } => {
663
+ let obj = self.emit_expr(object)?;
664
+ let val = self.emit_expr(value)?;
665
+ format!("({}.{} = {})", obj, prop.as_ref(), val)
666
+ }
667
+ Expr::IndexAssign {
668
+ object,
669
+ index,
670
+ value,
671
+ ..
672
+ } => {
673
+ let obj = self.emit_expr(object)?;
674
+ let idx = self.emit_expr(index)?;
675
+ let val = self.emit_expr(value)?;
676
+ format!("({}[{}] = {})", obj, idx, val)
677
+ }
678
+ Expr::ArrowFunction { params, body, .. } => {
679
+ let ps = self.emit_params(params, None)?;
680
+ let body_str = match body {
681
+ ArrowBody::Expr(e) => self.emit_expr(e)?,
682
+ ArrowBody::Block(s) => {
683
+ let saved = std::mem::take(&mut self.output);
684
+ self.writeln("{");
685
+ self.indent += 1;
686
+ self.emit_statement(s)?;
687
+ self.indent -= 1;
688
+ self.writeln("}");
689
+ let block = self.output.trim().to_string();
690
+ self.output = saved;
691
+ block
692
+ }
693
+ };
694
+ if matches!(body, ArrowBody::Expr(_)) {
695
+ format!("({}) => ({})", ps, body_str)
696
+ } else {
697
+ format!("({}) => {}", ps, body_str)
698
+ }
699
+ }
700
+ Expr::TemplateLiteral { quasis, exprs, .. } => {
701
+ let mut s = String::from('`');
702
+ for (i, q) in quasis.iter().enumerate() {
703
+ let escaped = q
704
+ .replace('\\', "\\\\")
705
+ .replace('`', "\\`")
706
+ .replace('$', "\\$");
707
+ s.push_str(&escaped);
708
+ if i < exprs.len() {
709
+ s.push_str("${");
710
+ s.push_str(&self.emit_expr(&exprs[i])?);
711
+ s.push('}');
712
+ }
713
+ }
714
+ s.push('`');
715
+ s
716
+ }
717
+ Expr::Await { operand, .. } => {
718
+ let o = self.emit_expr(operand)?;
719
+ format!("(await {})", o)
720
+ }
721
+ Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
722
+ tishlang_ui::jsx::emit_jsx_js(expr, &mut |e| {
723
+ self.emit_expr(e).map_err(|ce| ce.message)
724
+ })
725
+ .map_err(|m| CompileError { message: m })?
726
+ }
727
+ Expr::NativeModuleLoad { spec, .. } => {
728
+ return Err(CompileError {
729
+ message: format!(
730
+ "Native module imports ({}) are only supported when compiling to Rust. Omit --target js.",
731
+ spec.as_ref()
732
+ ),
733
+ });
734
+ }
735
+ })
736
+ }
737
+
738
+ fn emit_call_arg(&mut self, arg: &CallArg) -> Result<String, CompileError> {
739
+ match arg {
740
+ CallArg::Expr(e) => self.emit_expr(e),
741
+ CallArg::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
742
+ }
743
+ }
744
+ }
745
+
746
+ /// Compile a single program (no imports) to JavaScript. JSX lowers to `h` / `Fragment` (Lattish).
747
+ pub fn compile_with_jsx(program: &Program, optimize: bool) -> Result<String, CompileError> {
748
+ let program = if optimize {
749
+ tishlang_opt::optimize(program)
750
+ } else {
751
+ program.clone()
752
+ };
753
+ let mut g = Codegen::new();
754
+ g.emit_program(&program, None, None)?;
755
+ Ok(g.output)
756
+ }
757
+
758
+ /// JavaScript plus optional v3 source map JSON (for publishing Tish libraries consumed from JS/TS).
759
+ #[derive(Debug, Clone)]
760
+ pub struct JsBundle {
761
+ pub js: String,
762
+ pub source_map_json: Option<String>,
763
+ }
764
+
765
+ /// Same as [`compile_project_with_jsx`] plus a v3 source map pointing at merged statements’ original `.tish` files.
766
+ /// **Does not run AST optimization** (required so statement ↔ file alignment stays valid).
767
+ pub fn compile_project_with_jsx_and_source_map(
768
+ entry_path: &Path,
769
+ project_root: Option<&Path>,
770
+ output_js_file_name: &str,
771
+ ) -> Result<JsBundle, CompileError> {
772
+ compile_project_js_inner(entry_path, project_root, false, true, output_js_file_name)
773
+ }
774
+
775
+ fn compile_project_js_inner(
776
+ entry_path: &Path,
777
+ project_root: Option<&Path>,
778
+ optimize: bool,
779
+ emit_source_map: bool,
780
+ output_js_file_name: &str,
781
+ ) -> Result<JsBundle, CompileError> {
782
+ use tishlang_ast::Statement;
783
+ let modules = tishlang_compile::resolve_project(entry_path, project_root)
784
+ .map_err(|e| CompileError { message: e })?;
785
+ tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
786
+ let merged =
787
+ tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
788
+ let program = if optimize {
789
+ tishlang_opt::optimize(&merged.program)
790
+ } else {
791
+ merged.program.clone()
792
+ };
793
+ let stmt_sources = merged.statement_sources;
794
+ let default_export = program.statements.iter().find_map(|s| {
795
+ if let Statement::VarDecl { name, .. } = s {
796
+ let n = name.as_ref();
797
+ if n.starts_with("__default_") {
798
+ Some(n.to_string())
799
+ } else {
800
+ None
801
+ }
802
+ } else {
803
+ None
804
+ }
805
+ });
806
+ if emit_source_map && optimize {
807
+ return Err(CompileError {
808
+ message: "internal: source map requested with optimize".into(),
809
+ });
810
+ }
811
+ let root = project_root
812
+ .map(Path::to_path_buf)
813
+ .or_else(|| entry_path.parent().map(Path::to_path_buf))
814
+ .unwrap_or_else(|| PathBuf::from("."));
815
+ let mut gen = Codegen::new();
816
+ let mut map_builder = if emit_source_map {
817
+ let mut b = SourceMapBuilder::new(Some(output_js_file_name));
818
+ b.set_source_root(Some(""));
819
+ Some(b)
820
+ } else {
821
+ None
822
+ };
823
+ if let Some(ref mut b) = map_builder {
824
+ gen.emit_program(
825
+ &program,
826
+ Some((stmt_sources.as_slice(), root.as_path())),
827
+ Some(b),
828
+ )?;
829
+ } else {
830
+ gen.emit_program(&program, None, None)?;
831
+ }
832
+ let mut js = gen.output;
833
+ if let Some(name) = default_export {
834
+ js.push_str(&format!("\nexport default {};\n", name));
835
+ }
836
+ let map_json = if let Some(b) = map_builder {
837
+ let sm = b.into_sourcemap();
838
+ let mut v = Vec::new();
839
+ sm.to_writer(&mut v).map_err(|e| CompileError {
840
+ message: e.to_string(),
841
+ })?;
842
+ Some(String::from_utf8(v).map_err(|e| CompileError {
843
+ message: e.to_string(),
844
+ })?)
845
+ } else {
846
+ None
847
+ };
848
+ Ok(JsBundle {
849
+ js,
850
+ source_map_json: map_json,
851
+ })
852
+ }
853
+
854
+ /// Compile a project from entry path, resolving and merging modules.
855
+ /// Uses shared resolve from tishlang_compile (same pipeline as native/WASM).
856
+ pub fn compile_project_with_jsx(
857
+ entry_path: &std::path::Path,
858
+ project_root: Option<&std::path::Path>,
859
+ optimize: bool,
860
+ ) -> Result<String, CompileError> {
861
+ let stem = entry_path
862
+ .file_name()
863
+ .and_then(|s| s.to_str())
864
+ .unwrap_or("out.js");
865
+ let out_name = if stem.ends_with(".tish") {
866
+ format!("{}.js", stem.trim_end_matches(".tish"))
867
+ } else {
868
+ format!("{stem}.js")
869
+ };
870
+ Ok(compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js)
871
+ }