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