@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,943 @@
1
+ //! AST optimization pass for Tish.
2
+ //!
3
+ //! Applies constant folding, short-circuit evaluation, conditional simplification,
4
+ //! and dead code elimination. Benefits all backends: bytecode VM, native, Rust codegen, JS transpilation.
5
+
6
+ use std::sync::Arc;
7
+
8
+ use tishlang_ast::{ArrowBody, BinOp, Expr, Literal, Program, Statement, UnaryOp};
9
+
10
+ /// Optimize a Tish program. Returns a new program with transformations applied.
11
+ pub fn optimize(program: &Program) -> Program {
12
+ Program {
13
+ statements: program.statements.iter().map(optimize_statement).collect(),
14
+ }
15
+ }
16
+
17
+ fn optimize_statement(stmt: &Statement) -> Statement {
18
+ match stmt {
19
+ Statement::Block { statements, span } => {
20
+ let optimized = optimize_block(statements);
21
+ Statement::Block {
22
+ statements: optimized,
23
+ span: *span,
24
+ }
25
+ }
26
+ Statement::VarDecl {
27
+ name,
28
+ name_span,
29
+ mutable,
30
+ type_ann,
31
+ init,
32
+ span,
33
+ } => Statement::VarDecl {
34
+ name: Arc::clone(name),
35
+ name_span: *name_span,
36
+ mutable: *mutable,
37
+ type_ann: type_ann.clone(),
38
+ init: init.as_ref().map(optimize_expr),
39
+ span: *span,
40
+ },
41
+ Statement::VarDeclDestructure {
42
+ pattern,
43
+ mutable,
44
+ init,
45
+ span,
46
+ } => Statement::VarDeclDestructure {
47
+ pattern: pattern.clone(),
48
+ mutable: *mutable,
49
+ init: optimize_expr(init),
50
+ span: *span,
51
+ },
52
+ Statement::ExprStmt { expr, span } => Statement::ExprStmt {
53
+ expr: optimize_expr(expr),
54
+ span: *span,
55
+ },
56
+ Statement::If {
57
+ cond,
58
+ then_branch,
59
+ else_branch,
60
+ span,
61
+ } => {
62
+ let opt_cond = optimize_expr(cond);
63
+ // Conditional simplification: if cond is constant, take only the branch
64
+ if let Expr::Literal { value, .. } = &opt_cond {
65
+ let truthy = literal_is_truthy(value);
66
+ return if truthy {
67
+ optimize_statement(then_branch)
68
+ } else if let Some(else_b) = else_branch {
69
+ optimize_statement(else_b)
70
+ } else {
71
+ Statement::Block {
72
+ statements: vec![],
73
+ span: *span,
74
+ }
75
+ };
76
+ }
77
+ Statement::If {
78
+ cond: opt_cond,
79
+ then_branch: Box::new(optimize_statement(then_branch)),
80
+ else_branch: else_branch
81
+ .as_ref()
82
+ .map(|b| Box::new(optimize_statement(b))),
83
+ span: *span,
84
+ }
85
+ }
86
+ Statement::While { cond, body, span } => Statement::While {
87
+ cond: optimize_expr(cond),
88
+ body: Box::new(optimize_statement(body)),
89
+ span: *span,
90
+ },
91
+ Statement::For {
92
+ init,
93
+ cond,
94
+ update,
95
+ body,
96
+ span,
97
+ } => Statement::For {
98
+ init: init.as_ref().map(|i| Box::new(optimize_statement(i))),
99
+ cond: cond.as_ref().map(optimize_expr),
100
+ update: update.as_ref().map(optimize_expr),
101
+ body: Box::new(optimize_statement(body)),
102
+ span: *span,
103
+ },
104
+ Statement::ForOf {
105
+ name,
106
+ name_span,
107
+ iterable,
108
+ body,
109
+ span,
110
+ } => Statement::ForOf {
111
+ name: Arc::clone(name),
112
+ name_span: *name_span,
113
+ iterable: optimize_expr(iterable),
114
+ body: Box::new(optimize_statement(body)),
115
+ span: *span,
116
+ },
117
+ Statement::Return { value, span } => Statement::Return {
118
+ value: value.as_ref().map(optimize_expr),
119
+ span: *span,
120
+ },
121
+ Statement::Break { span } => Statement::Break { span: *span },
122
+ Statement::Continue { span } => Statement::Continue { span: *span },
123
+ Statement::FunDecl {
124
+ async_,
125
+ name,
126
+ name_span,
127
+ params,
128
+ rest_param,
129
+ return_type,
130
+ body,
131
+ span,
132
+ } => Statement::FunDecl {
133
+ async_: *async_,
134
+ name: Arc::clone(name),
135
+ name_span: *name_span,
136
+ params: params.clone(),
137
+ rest_param: rest_param.clone(),
138
+ return_type: return_type.clone(),
139
+ body: Box::new(optimize_statement(body)),
140
+ span: *span,
141
+ },
142
+ Statement::Switch {
143
+ expr,
144
+ cases,
145
+ default_body,
146
+ span,
147
+ } => Statement::Switch {
148
+ expr: optimize_expr(expr),
149
+ cases: cases
150
+ .iter()
151
+ .map(|(ce, stmts)| (ce.as_ref().map(optimize_expr), optimize_block(stmts)))
152
+ .collect(),
153
+ default_body: default_body.as_ref().map(|stmts| optimize_block(stmts)),
154
+ span: *span,
155
+ },
156
+ Statement::DoWhile { body, cond, span } => Statement::DoWhile {
157
+ body: Box::new(optimize_statement(body)),
158
+ cond: optimize_expr(cond),
159
+ span: *span,
160
+ },
161
+ Statement::Throw { value, span } => Statement::Throw {
162
+ value: optimize_expr(value),
163
+ span: *span,
164
+ },
165
+ Statement::Try {
166
+ body,
167
+ catch_param,
168
+ catch_param_span,
169
+ catch_body,
170
+ finally_body,
171
+ span,
172
+ } => Statement::Try {
173
+ body: Box::new(optimize_statement(body)),
174
+ catch_param: catch_param.clone(),
175
+ catch_param_span: *catch_param_span,
176
+ catch_body: catch_body.as_ref().map(|b| Box::new(optimize_statement(b))),
177
+ finally_body: finally_body
178
+ .as_ref()
179
+ .map(|b| Box::new(optimize_statement(b))),
180
+ span: *span,
181
+ },
182
+ Statement::Import { .. }
183
+ | Statement::Export { .. }
184
+ | Statement::TypeAlias { .. }
185
+ | Statement::DeclareVar { .. }
186
+ | Statement::DeclareFun { .. } => stmt.clone(),
187
+ }
188
+ }
189
+
190
+ /// Optimize block with dead code elimination: remove statements after return/throw.
191
+ fn optimize_block(statements: &[Statement]) -> Vec<Statement> {
192
+ let mut result = Vec::new();
193
+ for stmt in statements {
194
+ if let Some(last) = result.last() {
195
+ if stmt_always_returns_or_throws(last) {
196
+ // Dead code - skip
197
+ continue;
198
+ }
199
+ }
200
+ result.push(optimize_statement(stmt));
201
+ }
202
+ result
203
+ }
204
+
205
+ fn stmt_always_returns_or_throws(stmt: &Statement) -> bool {
206
+ match stmt {
207
+ Statement::Return { .. } | Statement::Throw { .. } => true,
208
+ Statement::If {
209
+ cond: Expr::Literal { value, .. },
210
+ then_branch,
211
+ else_branch,
212
+ ..
213
+ } => {
214
+ let truthy = literal_is_truthy(value);
215
+ if truthy {
216
+ stmt_always_returns_or_throws(then_branch)
217
+ } else if let Some(else_b) = else_branch {
218
+ stmt_always_returns_or_throws(else_b)
219
+ } else {
220
+ false
221
+ }
222
+ }
223
+ Statement::If { .. } => false,
224
+ _ => false,
225
+ }
226
+ }
227
+
228
+ fn optimize_expr(expr: &Expr) -> Expr {
229
+ match expr {
230
+ Expr::Literal { value, span } => Expr::Literal {
231
+ value: value.clone(),
232
+ span: *span,
233
+ },
234
+ Expr::Ident { name, span } => Expr::Ident {
235
+ name: Arc::clone(name),
236
+ span: *span,
237
+ },
238
+ Expr::Binary {
239
+ left,
240
+ op,
241
+ right,
242
+ span,
243
+ } => {
244
+ let opt_left = optimize_expr(left);
245
+ let opt_right = optimize_expr(right);
246
+
247
+ // Short-circuit for And/Or when left is constant
248
+ if *op == BinOp::And {
249
+ if let Expr::Literal { value, .. } = &opt_left {
250
+ return if literal_is_truthy(value) {
251
+ opt_right
252
+ } else {
253
+ Expr::Literal {
254
+ value: Literal::Bool(false),
255
+ span: *span,
256
+ }
257
+ };
258
+ }
259
+ }
260
+ if *op == BinOp::Or {
261
+ if let Expr::Literal { value, .. } = &opt_left {
262
+ return if literal_is_truthy(value) {
263
+ Expr::Literal {
264
+ value: Literal::Bool(true),
265
+ span: *span,
266
+ }
267
+ } else {
268
+ opt_right
269
+ };
270
+ }
271
+ }
272
+
273
+ // Constant folding when both are literals
274
+ if let (Expr::Literal { value: lv, .. }, Expr::Literal { value: rv, .. }) =
275
+ (&opt_left, &opt_right)
276
+ {
277
+ if let Some(folded) = try_fold_binop(lv, *op, rv) {
278
+ return Expr::Literal {
279
+ value: folded,
280
+ span: *span,
281
+ };
282
+ }
283
+ }
284
+
285
+ // A5: Algebraic simplification (x+0=x, x*1=x, etc.).
286
+ // Applied after constant folding so e.g. x*(1+0) → x*1 → x.
287
+ if let Some(simplified) = try_algebraic_simplify(*op, &opt_left, &opt_right, *span) {
288
+ return simplified;
289
+ }
290
+
291
+ Expr::Binary {
292
+ left: Box::new(opt_left),
293
+ op: *op,
294
+ right: Box::new(opt_right),
295
+ span: *span,
296
+ }
297
+ }
298
+ Expr::Unary { op, operand, span } => {
299
+ let opt_operand = optimize_expr(operand);
300
+ if let Expr::Literal { value, .. } = &opt_operand {
301
+ if let Some(folded) = try_fold_unary(*op, value) {
302
+ return Expr::Literal {
303
+ value: folded,
304
+ span: *span,
305
+ };
306
+ }
307
+ }
308
+ Expr::Unary {
309
+ op: *op,
310
+ operand: Box::new(opt_operand),
311
+ span: *span,
312
+ }
313
+ }
314
+ Expr::Conditional {
315
+ cond,
316
+ then_branch,
317
+ else_branch,
318
+ span,
319
+ } => {
320
+ let opt_cond = optimize_expr(cond);
321
+ if let Expr::Literal { value, .. } = &opt_cond {
322
+ return if literal_is_truthy(value) {
323
+ optimize_expr(then_branch)
324
+ } else {
325
+ optimize_expr(else_branch)
326
+ };
327
+ }
328
+ Expr::Conditional {
329
+ cond: Box::new(opt_cond),
330
+ then_branch: Box::new(optimize_expr(then_branch)),
331
+ else_branch: Box::new(optimize_expr(else_branch)),
332
+ span: *span,
333
+ }
334
+ }
335
+ Expr::Call { callee, args, span } => Expr::Call {
336
+ callee: Box::new(optimize_expr(callee)),
337
+ args: args
338
+ .iter()
339
+ .map(|a| match a {
340
+ tishlang_ast::CallArg::Expr(e) => tishlang_ast::CallArg::Expr(optimize_expr(e)),
341
+ tishlang_ast::CallArg::Spread(e) => {
342
+ tishlang_ast::CallArg::Spread(optimize_expr(e))
343
+ }
344
+ })
345
+ .collect(),
346
+ span: *span,
347
+ },
348
+ Expr::New { callee, args, span } => Expr::New {
349
+ callee: Box::new(optimize_expr(callee)),
350
+ args: args
351
+ .iter()
352
+ .map(|a| match a {
353
+ tishlang_ast::CallArg::Expr(e) => tishlang_ast::CallArg::Expr(optimize_expr(e)),
354
+ tishlang_ast::CallArg::Spread(e) => {
355
+ tishlang_ast::CallArg::Spread(optimize_expr(e))
356
+ }
357
+ })
358
+ .collect(),
359
+ span: *span,
360
+ },
361
+ Expr::Member {
362
+ object,
363
+ prop,
364
+ optional,
365
+ span,
366
+ } => {
367
+ let opt_obj = optimize_expr(object);
368
+ let opt_prop = match prop {
369
+ tishlang_ast::MemberProp::Name { name, span } => tishlang_ast::MemberProp::Name {
370
+ name: Arc::clone(name),
371
+ span: *span,
372
+ },
373
+ tishlang_ast::MemberProp::Expr(e) => {
374
+ tishlang_ast::MemberProp::Expr(Box::new(optimize_expr(e)))
375
+ }
376
+ };
377
+ Expr::Member {
378
+ object: Box::new(opt_obj),
379
+ prop: opt_prop,
380
+ optional: *optional,
381
+ span: *span,
382
+ }
383
+ }
384
+ Expr::Index {
385
+ object,
386
+ index,
387
+ optional,
388
+ span,
389
+ } => Expr::Index {
390
+ object: Box::new(optimize_expr(object)),
391
+ index: Box::new(optimize_expr(index)),
392
+ optional: *optional,
393
+ span: *span,
394
+ },
395
+ Expr::NullishCoalesce { left, right, span } => {
396
+ let opt_left = optimize_expr(left);
397
+ if let Expr::Literal {
398
+ value: Literal::Null,
399
+ ..
400
+ } = &opt_left
401
+ {
402
+ return optimize_expr(right);
403
+ }
404
+ Expr::NullishCoalesce {
405
+ left: Box::new(opt_left),
406
+ right: Box::new(optimize_expr(right)),
407
+ span: *span,
408
+ }
409
+ }
410
+ Expr::Array { elements, span } => Expr::Array {
411
+ elements: elements
412
+ .iter()
413
+ .map(|e| match e {
414
+ tishlang_ast::ArrayElement::Expr(ex) => {
415
+ tishlang_ast::ArrayElement::Expr(optimize_expr(ex))
416
+ }
417
+ tishlang_ast::ArrayElement::Spread(ex) => {
418
+ tishlang_ast::ArrayElement::Spread(optimize_expr(ex))
419
+ }
420
+ })
421
+ .collect(),
422
+ span: *span,
423
+ },
424
+ Expr::Object { props, span } => Expr::Object {
425
+ props: props
426
+ .iter()
427
+ .map(|p| match p {
428
+ tishlang_ast::ObjectProp::KeyValue(k, v) => {
429
+ tishlang_ast::ObjectProp::KeyValue(Arc::clone(k), optimize_expr(v))
430
+ }
431
+ tishlang_ast::ObjectProp::Spread(e) => {
432
+ tishlang_ast::ObjectProp::Spread(optimize_expr(e))
433
+ }
434
+ })
435
+ .collect(),
436
+ span: *span,
437
+ },
438
+ Expr::Assign { name, value, span } => Expr::Assign {
439
+ name: Arc::clone(name),
440
+ value: Box::new(optimize_expr(value)),
441
+ span: *span,
442
+ },
443
+ Expr::TypeOf { operand, span } => Expr::TypeOf {
444
+ operand: Box::new(optimize_expr(operand)),
445
+ span: *span,
446
+ },
447
+ Expr::PostfixInc { .. }
448
+ | Expr::PostfixDec { .. }
449
+ | Expr::PrefixInc { .. }
450
+ | Expr::PrefixDec { .. } => expr.clone(),
451
+ Expr::CompoundAssign {
452
+ name,
453
+ op,
454
+ value,
455
+ span,
456
+ } => Expr::CompoundAssign {
457
+ name: Arc::clone(name),
458
+ op: *op,
459
+ value: Box::new(optimize_expr(value)),
460
+ span: *span,
461
+ },
462
+ Expr::LogicalAssign {
463
+ name,
464
+ op,
465
+ value,
466
+ span,
467
+ } => Expr::LogicalAssign {
468
+ name: Arc::clone(name),
469
+ op: *op,
470
+ value: Box::new(optimize_expr(value)),
471
+ span: *span,
472
+ },
473
+ Expr::MemberAssign {
474
+ object,
475
+ prop,
476
+ value,
477
+ span,
478
+ } => Expr::MemberAssign {
479
+ object: Box::new(optimize_expr(object)),
480
+ prop: Arc::clone(prop),
481
+ value: Box::new(optimize_expr(value)),
482
+ span: *span,
483
+ },
484
+ Expr::IndexAssign {
485
+ object,
486
+ index,
487
+ value,
488
+ span,
489
+ } => Expr::IndexAssign {
490
+ object: Box::new(optimize_expr(object)),
491
+ index: Box::new(optimize_expr(index)),
492
+ value: Box::new(optimize_expr(value)),
493
+ span: *span,
494
+ },
495
+ Expr::ArrowFunction { params, body, span } => {
496
+ let opt_body = match body {
497
+ ArrowBody::Expr(e) => ArrowBody::Expr(Box::new(optimize_expr(e))),
498
+ ArrowBody::Block(s) => ArrowBody::Block(Box::new(optimize_statement(s))),
499
+ };
500
+ Expr::ArrowFunction {
501
+ params: params.clone(),
502
+ body: opt_body,
503
+ span: *span,
504
+ }
505
+ }
506
+ Expr::TemplateLiteral {
507
+ quasis,
508
+ exprs,
509
+ span,
510
+ } => Expr::TemplateLiteral {
511
+ quasis: quasis.iter().map(Arc::clone).collect(),
512
+ exprs: exprs.iter().map(optimize_expr).collect(),
513
+ span: *span,
514
+ },
515
+ Expr::Await { operand, span } => Expr::Await {
516
+ operand: Box::new(optimize_expr(operand)),
517
+ span: *span,
518
+ },
519
+ Expr::JsxElement { .. } | Expr::JsxFragment { .. } => expr.clone(),
520
+ Expr::NativeModuleLoad {
521
+ spec,
522
+ export_name,
523
+ span,
524
+ } => Expr::NativeModuleLoad {
525
+ spec: Arc::clone(spec),
526
+ export_name: Arc::clone(export_name),
527
+ span: *span,
528
+ },
529
+ }
530
+ }
531
+
532
+ fn literal_is_truthy(lit: &Literal) -> bool {
533
+ match lit {
534
+ Literal::Null => false,
535
+ Literal::Bool(b) => *b,
536
+ Literal::Number(n) => *n != 0.0 && !n.is_nan(),
537
+ Literal::String(s) => !s.is_empty(),
538
+ }
539
+ }
540
+
541
+ fn literal_strict_eq(a: &Literal, b: &Literal) -> bool {
542
+ match (a, b) {
543
+ (Literal::Number(x), Literal::Number(y)) => {
544
+ if x.is_nan() || y.is_nan() {
545
+ false
546
+ } else {
547
+ x == y
548
+ }
549
+ }
550
+ (Literal::String(x), Literal::String(y)) => x == y,
551
+ (Literal::Bool(x), Literal::Bool(y)) => x == y,
552
+ (Literal::Null, Literal::Null) => true,
553
+ _ => false,
554
+ }
555
+ }
556
+
557
+ fn literal_to_display_string(lit: &Literal) -> String {
558
+ match lit {
559
+ Literal::Number(n) => {
560
+ if n.is_nan() {
561
+ "NaN".to_string()
562
+ } else if *n == f64::INFINITY {
563
+ "Infinity".to_string()
564
+ } else if *n == f64::NEG_INFINITY {
565
+ "-Infinity".to_string()
566
+ } else {
567
+ n.to_string()
568
+ }
569
+ }
570
+ Literal::String(s) => s.to_string(),
571
+ Literal::Bool(b) => b.to_string(),
572
+ Literal::Null => "null".to_string(),
573
+ }
574
+ }
575
+
576
+ fn literal_as_number(lit: &Literal) -> f64 {
577
+ match lit {
578
+ Literal::Number(n) => *n,
579
+ Literal::Bool(true) => 1.0,
580
+ Literal::Bool(false) => 0.0,
581
+ Literal::Null => 0.0,
582
+ Literal::String(s) => s.parse().unwrap_or(f64::NAN),
583
+ }
584
+ }
585
+
586
+ /// Algebraic simplification: x+0→x, x*1→x, etc.
587
+ /// Only applies when the literal is a clean 0 or 1 (no NaN/Inf).
588
+ fn try_algebraic_simplify(
589
+ op: BinOp,
590
+ left: &Expr,
591
+ right: &Expr,
592
+ span: tishlang_ast::Span,
593
+ ) -> Option<Expr> {
594
+ use BinOp::*;
595
+ fn num_is_zero(n: f64) -> bool {
596
+ n == 0.0 && !n.is_nan() && n.is_finite()
597
+ }
598
+ fn num_is_one(n: f64) -> bool {
599
+ (n - 1.0).abs() < f64::EPSILON && !n.is_nan() && n.is_finite()
600
+ }
601
+
602
+ match op {
603
+ Add => {
604
+ if let Expr::Literal {
605
+ value: Literal::Number(r),
606
+ ..
607
+ } = right
608
+ {
609
+ if num_is_zero(*r) {
610
+ return Some(left.clone());
611
+ }
612
+ }
613
+ if let Expr::Literal {
614
+ value: Literal::Number(l),
615
+ ..
616
+ } = left
617
+ {
618
+ if num_is_zero(*l) {
619
+ return Some(right.clone());
620
+ }
621
+ }
622
+ }
623
+ Sub => {
624
+ if let Expr::Literal {
625
+ value: Literal::Number(r),
626
+ ..
627
+ } = right
628
+ {
629
+ if num_is_zero(*r) {
630
+ return Some(left.clone());
631
+ }
632
+ }
633
+ }
634
+ Mul => {
635
+ if let Expr::Literal {
636
+ value: Literal::Number(r),
637
+ ..
638
+ } = right
639
+ {
640
+ if num_is_one(*r) {
641
+ return Some(left.clone());
642
+ }
643
+ if num_is_zero(*r) {
644
+ return Some(Expr::Literal {
645
+ value: Literal::Number(0.0),
646
+ span,
647
+ });
648
+ }
649
+ }
650
+ if let Expr::Literal {
651
+ value: Literal::Number(l),
652
+ ..
653
+ } = left
654
+ {
655
+ if num_is_one(*l) {
656
+ return Some(right.clone());
657
+ }
658
+ if num_is_zero(*l) {
659
+ return Some(Expr::Literal {
660
+ value: Literal::Number(0.0),
661
+ span,
662
+ });
663
+ }
664
+ }
665
+ }
666
+ Div => {
667
+ if let Expr::Literal {
668
+ value: Literal::Number(r),
669
+ ..
670
+ } = right
671
+ {
672
+ if num_is_one(*r) {
673
+ return Some(left.clone());
674
+ }
675
+ }
676
+ }
677
+ Pow => {
678
+ if let Expr::Literal {
679
+ value: Literal::Number(r),
680
+ ..
681
+ } = right
682
+ {
683
+ if num_is_one(*r) {
684
+ return Some(left.clone());
685
+ }
686
+ if num_is_zero(*r) {
687
+ return Some(Expr::Literal {
688
+ value: Literal::Number(1.0),
689
+ span,
690
+ });
691
+ }
692
+ }
693
+ if let Expr::Literal {
694
+ value: Literal::Number(l),
695
+ ..
696
+ } = left
697
+ {
698
+ if num_is_one(*l) {
699
+ return Some(Expr::Literal {
700
+ value: Literal::Number(1.0),
701
+ span,
702
+ });
703
+ }
704
+ }
705
+ }
706
+ _ => {}
707
+ }
708
+ None
709
+ }
710
+
711
+ fn try_fold_binop(left: &Literal, op: BinOp, right: &Literal) -> Option<Literal> {
712
+ use BinOp::*;
713
+ let ln = literal_as_number(left);
714
+ let rn = literal_as_number(right);
715
+
716
+ let result = match op {
717
+ Add => {
718
+ if matches!(left, Literal::String(_)) || matches!(right, Literal::String(_)) {
719
+ return Some(Literal::String(
720
+ format!(
721
+ "{}{}",
722
+ literal_to_display_string(left),
723
+ literal_to_display_string(right)
724
+ )
725
+ .into(),
726
+ ));
727
+ }
728
+ Literal::Number(ln + rn)
729
+ }
730
+ Sub => Literal::Number(ln - rn),
731
+ Mul => Literal::Number(ln * rn),
732
+ Div => Literal::Number(if rn == 0.0 { f64::NAN } else { ln / rn }),
733
+ Mod => Literal::Number(if rn == 0.0 { f64::NAN } else { ln % rn }),
734
+ Pow => Literal::Number(ln.powf(rn)),
735
+ Eq => Literal::Bool(literal_strict_eq(left, right)),
736
+ Ne => Literal::Bool(!literal_strict_eq(left, right)),
737
+ StrictEq => Literal::Bool(literal_strict_eq(left, right)),
738
+ StrictNe => Literal::Bool(!literal_strict_eq(left, right)),
739
+ Lt => Literal::Bool(ln < rn),
740
+ Le => Literal::Bool(ln <= rn),
741
+ Gt => Literal::Bool(ln > rn),
742
+ Ge => Literal::Bool(ln >= rn),
743
+ And => Literal::Bool(literal_is_truthy(left) && literal_is_truthy(right)),
744
+ Or => Literal::Bool(literal_is_truthy(left) || literal_is_truthy(right)),
745
+ BitAnd => Literal::Number((ln as i32 & rn as i32) as f64),
746
+ BitOr => Literal::Number((ln as i32 | rn as i32) as f64),
747
+ BitXor => Literal::Number((ln as i32 ^ rn as i32) as f64),
748
+ Shl => Literal::Number(((ln as i32) << (rn as i32)) as f64),
749
+ Shr => Literal::Number(((ln as i32) >> (rn as i32)) as f64),
750
+ In => return None, // Requires object/array on right
751
+ };
752
+ Some(result)
753
+ }
754
+
755
+ #[cfg(test)]
756
+ mod tests {
757
+ use super::*;
758
+
759
+ fn program_from_source(src: &str) -> Program {
760
+ tishlang_parser::parse(src).expect("parse")
761
+ }
762
+
763
+ fn has_literal_number(expr: &Expr, n: f64) -> bool {
764
+ if let Expr::Literal {
765
+ value: Literal::Number(x),
766
+ ..
767
+ } = expr
768
+ {
769
+ (*x - n).abs() < f64::EPSILON
770
+ } else {
771
+ false
772
+ }
773
+ }
774
+
775
+ #[test]
776
+ fn constant_fold_add() {
777
+ let program = program_from_source("1 + 2");
778
+ let opt = optimize(&program);
779
+ let expr = match &opt.statements[..] {
780
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
781
+ _ => panic!("expected single expr stmt"),
782
+ };
783
+ assert!(has_literal_number(expr, 3.0), "expected 3, got {:?}", expr);
784
+ }
785
+
786
+ #[test]
787
+ fn constant_fold_unary_neg() {
788
+ let program = program_from_source("-42");
789
+ let opt = optimize(&program);
790
+ let expr = match &opt.statements[..] {
791
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
792
+ _ => panic!("expected single expr stmt"),
793
+ };
794
+ assert!(
795
+ has_literal_number(expr, -42.0),
796
+ "expected -42, got {:?}",
797
+ expr
798
+ );
799
+ }
800
+
801
+ #[test]
802
+ fn short_circuit_false_and() {
803
+ let program = program_from_source("false && foo");
804
+ let opt = optimize(&program);
805
+ let expr = match &opt.statements[..] {
806
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
807
+ _ => panic!("expected single expr stmt"),
808
+ };
809
+ assert!(
810
+ matches!(
811
+ expr,
812
+ Expr::Literal {
813
+ value: Literal::Bool(false),
814
+ ..
815
+ }
816
+ ),
817
+ "expected false, got {:?}",
818
+ expr
819
+ );
820
+ }
821
+
822
+ #[test]
823
+ fn conditional_simplify_true() {
824
+ let program = program_from_source("true ? 1 : 2");
825
+ let opt = optimize(&program);
826
+ let expr = match &opt.statements[..] {
827
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
828
+ _ => panic!("expected single expr stmt"),
829
+ };
830
+ assert!(has_literal_number(expr, 1.0), "expected 1, got {:?}", expr);
831
+ }
832
+
833
+ #[test]
834
+ fn algebraic_simplify_x_plus_zero() {
835
+ // x + 0 → x (after constant fold, 0 is literal)
836
+ let program = program_from_source("x + 0");
837
+ let opt = optimize(&program);
838
+ let expr = match &opt.statements[..] {
839
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
840
+ _ => panic!("expected single expr stmt"),
841
+ };
842
+ assert!(
843
+ matches!(expr, Expr::Ident { name, .. } if name.as_ref() == "x"),
844
+ "expected Ident(x), got {:?}",
845
+ expr
846
+ );
847
+ }
848
+
849
+ #[test]
850
+ fn algebraic_simplify_x_times_one() {
851
+ let program = program_from_source("x * 1");
852
+ let opt = optimize(&program);
853
+ let expr = match &opt.statements[..] {
854
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
855
+ _ => panic!("expected single expr stmt"),
856
+ };
857
+ assert!(
858
+ matches!(expr, Expr::Ident { name, .. } if name.as_ref() == "x"),
859
+ "expected Ident(x), got {:?}",
860
+ expr
861
+ );
862
+ }
863
+
864
+ #[test]
865
+ fn algebraic_simplify_chain() {
866
+ // x * (1 + 0) → constant fold 1+0=1 → x*1 → x
867
+ let program = program_from_source("x * (1 + 0)");
868
+ let opt = optimize(&program);
869
+ let expr = match &opt.statements[..] {
870
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
871
+ _ => panic!("expected single expr stmt"),
872
+ };
873
+ assert!(
874
+ matches!(expr, Expr::Ident { name, .. } if name.as_ref() == "x"),
875
+ "expected Ident(x) after x*(1+0) → x*1 → x, got {:?}",
876
+ expr
877
+ );
878
+ }
879
+
880
+ #[test]
881
+ fn algebraic_simplify_pow_one() {
882
+ let program = program_from_source("x ** 1");
883
+ let opt = optimize(&program);
884
+ let expr = match &opt.statements[..] {
885
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
886
+ _ => panic!("expected single expr stmt"),
887
+ };
888
+ assert!(
889
+ matches!(expr, Expr::Ident { name, .. } if name.as_ref() == "x"),
890
+ "expected Ident(x), got {:?}",
891
+ expr
892
+ );
893
+ }
894
+
895
+ #[test]
896
+ fn algebraic_simplify_pow_zero() {
897
+ let program = program_from_source("x ** 0");
898
+ let opt = optimize(&program);
899
+ let expr = match &opt.statements[..] {
900
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
901
+ _ => panic!("expected single expr stmt"),
902
+ };
903
+ assert!(has_literal_number(expr, 1.0), "expected 1, got {:?}", expr);
904
+ }
905
+
906
+ #[test]
907
+ fn algebraic_simplify_one_pow_x() {
908
+ let program = program_from_source("1 ** x");
909
+ let opt = optimize(&program);
910
+ let expr = match &opt.statements[..] {
911
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
912
+ _ => panic!("expected single expr stmt"),
913
+ };
914
+ assert!(has_literal_number(expr, 1.0), "expected 1, got {:?}", expr);
915
+ }
916
+
917
+ #[test]
918
+ fn nullish_coalesce_null_simplify() {
919
+ let program = program_from_source("null ?? x");
920
+ let opt = optimize(&program);
921
+ let expr = match &opt.statements[..] {
922
+ [tishlang_ast::Statement::ExprStmt { expr, .. }] => expr,
923
+ _ => panic!("expected single expr stmt"),
924
+ };
925
+ assert!(
926
+ matches!(expr, Expr::Ident { name, .. } if name.as_ref() == "x"),
927
+ "expected Ident(x), got {:?}",
928
+ expr
929
+ );
930
+ }
931
+ }
932
+
933
+ fn try_fold_unary(op: UnaryOp, operand: &Literal) -> Option<Literal> {
934
+ use UnaryOp::*;
935
+ let result = match op {
936
+ Not => Literal::Bool(!literal_is_truthy(operand)),
937
+ Neg => Literal::Number(-literal_as_number(operand)),
938
+ Pos => Literal::Number(literal_as_number(operand)),
939
+ BitNot => Literal::Number(!(literal_as_number(operand) as i32) as f64),
940
+ Void => Literal::Null,
941
+ };
942
+ Some(result)
943
+ }