@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,1760 @@
1
+ //! AST to bytecode compiler.
2
+
3
+ use std::collections::HashMap;
4
+ use std::sync::Arc;
5
+
6
+ use tishlang_ast::{
7
+ ArrayElement, ArrowBody, BinOp, CallArg, DestructElement, DestructPattern, ExportDeclaration,
8
+ Expr, FunParam, JsxAttrValue, JsxChild, JsxProp, Literal, LogicalAssignOp, MemberProp,
9
+ ObjectProp, Program, Span, Statement,
10
+ };
11
+
12
+ use crate::chunk::{Chunk, Constant};
13
+ use crate::encoding::{binop_to_u8, compound_op_to_u8, unaryop_to_u8};
14
+ use crate::opcode::Opcode;
15
+
16
+ enum SimpleMapResult {
17
+ Identity,
18
+ BinOp(BinOp, Constant, bool), // op, constant, param_on_left
19
+ }
20
+
21
+ fn literal_to_constant(expr: &Expr) -> Option<Constant> {
22
+ if let Expr::Literal { value, .. } = expr {
23
+ Some(match value {
24
+ Literal::Number(n) => Constant::Number(*n),
25
+ Literal::String(s) => Constant::String(Arc::clone(s)),
26
+ Literal::Bool(b) => Constant::Bool(*b),
27
+ Literal::Null => Constant::Null,
28
+ })
29
+ } else {
30
+ None
31
+ }
32
+ }
33
+
34
+ #[derive(Debug)]
35
+ pub struct CompileError {
36
+ pub message: String,
37
+ }
38
+
39
+ impl std::fmt::Display for CompileError {
40
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41
+ write!(f, "{}", self.message)
42
+ }
43
+ }
44
+
45
+ impl std::error::Error for CompileError {}
46
+
47
+ /// Loop boundary for break/continue.
48
+ struct LoopInfo {
49
+ break_patches: Vec<usize>,
50
+ /// Operand positions for `continue`: either `JumpBack` (while / do-while / for-of) or `Jump`
51
+ /// (C-style `for`, where the update clause is emitted after the body).
52
+ continue_patches: Vec<usize>,
53
+ /// When true, [`Opcode::Jump`] placeholders in `continue_patches` are patched forward with
54
+ /// [`Self::patch_jump`]. When false, they are [`Opcode::JumpBack`] patched with
55
+ /// [`Self::patch_jump_back`].
56
+ continue_is_forward_jump: bool,
57
+ }
58
+
59
+ /// Switch boundary: break exits the switch.
60
+ struct SwitchInfo {
61
+ break_patches: Vec<usize>,
62
+ }
63
+
64
+ /// Innermost break/continue target for unwinding `EnterBlock` before a jump.
65
+ #[derive(Clone, Copy)]
66
+ enum Breakable {
67
+ /// `usize` = `block_depth` before the loop body (same as Continue unwind target).
68
+ Loop { unwind_depth: usize },
69
+ /// `usize` = `block_depth` before the switch statement.
70
+ Switch { unwind_depth: usize },
71
+ }
72
+
73
+ struct Compiler<'a> {
74
+ chunk: &'a mut Chunk,
75
+ /// Current scope: variable name -> (depth, is_captured). Depth 0 = local.
76
+ scope: Vec<HashMap<Arc<str>, bool>>,
77
+ /// Stack of loop info for break/continue.
78
+ loop_stack: Vec<LoopInfo>,
79
+ switch_stack: Vec<SwitchInfo>,
80
+ /// Parallel to nested loops/switches: innermost target for break/continue block unwind.
81
+ breakable_stack: Vec<Breakable>,
82
+ /// Nesting depth of emitted `EnterBlock` (lexical blocks) not yet closed on the compile path.
83
+ block_depth: usize,
84
+ /// When true (REPL mode), last ExprStmt leaves its value on the stack and we skip trailing LoadConst Null.
85
+ retain_last_expr: bool,
86
+ }
87
+
88
+ impl<'a> Compiler<'a> {
89
+ fn new(chunk: &'a mut Chunk, retain_last_expr: bool) -> Self {
90
+ Self {
91
+ chunk,
92
+ scope: vec![HashMap::new()],
93
+ loop_stack: Vec::new(),
94
+ switch_stack: Vec::new(),
95
+ breakable_stack: Vec::new(),
96
+ block_depth: 0,
97
+ retain_last_expr,
98
+ }
99
+ }
100
+
101
+ fn emit_exit_blocks_until_depth(&mut self, target_depth: usize) {
102
+ let n = self.block_depth.saturating_sub(target_depth);
103
+ for _ in 0..n {
104
+ self.emit(Opcode::ExitBlock);
105
+ }
106
+ }
107
+
108
+ /// C-style `for` init: bindings are not inside the `{ ... }` body for block-undo purposes.
109
+ /// Formal parameters as VM slot names plus optional destructure patterns (one per formal).
110
+ fn plan_function_params(
111
+ params: &[FunParam],
112
+ ) -> Result<(Vec<Arc<str>>, Vec<Option<DestructPattern>>), CompileError> {
113
+ let mut names = Vec::with_capacity(params.len());
114
+ let mut slots: Vec<Option<DestructPattern>> = Vec::with_capacity(params.len());
115
+ let mut syn_counter = 0u32;
116
+ for p in params {
117
+ match p {
118
+ FunParam::Simple(tp) => {
119
+ names.push(Arc::clone(&tp.name));
120
+ slots.push(None);
121
+ }
122
+ FunParam::Destructure {
123
+ pattern, default, ..
124
+ } => {
125
+ if default.is_some() {
126
+ return Err(CompileError {
127
+ message: "Default values on destructuring parameters are not supported in bytecode"
128
+ .to_string(),
129
+ });
130
+ }
131
+ names.push(Arc::from(format!("__param_{}", syn_counter)));
132
+ syn_counter += 1;
133
+ slots.push(Some(pattern.clone()));
134
+ }
135
+ }
136
+ }
137
+ Ok((names, slots))
138
+ }
139
+
140
+ /// After VM binds positional args to `param_names`, load each destructure slot and bind pattern locals.
141
+ fn emit_param_destructure_prologue(
142
+ &mut self,
143
+ param_names: &[Arc<str>],
144
+ slots: &[Option<DestructPattern>],
145
+ ) -> Result<(), CompileError> {
146
+ debug_assert_eq!(param_names.len(), slots.len());
147
+ for (name, slot) in param_names.iter().zip(slots.iter()) {
148
+ if let Some(pattern) = slot {
149
+ let idx = self.name_idx(name);
150
+ self.emit_u16(Opcode::LoadVar, idx);
151
+ self.compile_destructure(pattern, false, false)?;
152
+ }
153
+ }
154
+ Ok(())
155
+ }
156
+
157
+ fn compile_for_init_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
158
+ match stmt {
159
+ Statement::VarDecl {
160
+ name,
161
+ init,
162
+ mutable: _,
163
+ ..
164
+ } => {
165
+ if let Some(expr) = init {
166
+ self.compile_expr(expr)?;
167
+ } else {
168
+ let idx = self.constant_idx(Constant::Null);
169
+ self.emit(Opcode::LoadConst);
170
+ self.chunk.write_u16(idx);
171
+ }
172
+ let idx = self.name_idx(name);
173
+ self.emit_u16(Opcode::DeclareVarPlain, idx);
174
+ self.scope
175
+ .last_mut()
176
+ .unwrap()
177
+ .insert(Arc::clone(name), false);
178
+ }
179
+ Statement::VarDeclDestructure { pattern, init, .. } => {
180
+ self.compile_expr(init)?;
181
+ self.compile_destructure(pattern, false, true)?;
182
+ }
183
+ _ => self.compile_statement(stmt)?,
184
+ }
185
+ Ok(())
186
+ }
187
+
188
+ fn name_idx(&mut self, name: &Arc<str>) -> u16 {
189
+ self.chunk.add_name(Arc::clone(name))
190
+ }
191
+
192
+ fn constant_idx(&mut self, c: Constant) -> u16 {
193
+ self.chunk.add_constant(c)
194
+ }
195
+
196
+ fn emit(&mut self, op: Opcode) {
197
+ self.chunk.write_u8(op as u8);
198
+ }
199
+
200
+ fn emit_u8(&mut self, op: Opcode, v: u8) {
201
+ self.chunk.write_u8(op as u8);
202
+ self.chunk.write_u16(v as u16);
203
+ }
204
+
205
+ fn emit_u16(&mut self, op: Opcode, v: u16) {
206
+ self.chunk.write_u8(op as u8);
207
+ self.chunk.write_u16(v);
208
+ }
209
+
210
+ fn emit_jump(&mut self, op: Opcode) -> usize {
211
+ let pos = self.chunk.code.len();
212
+ self.chunk.write_u8(op as u8);
213
+ self.chunk.write_u16(0); // placeholder
214
+ pos + 1
215
+ }
216
+
217
+ /// Emit JumpBack with placeholder distance; patch later with patch_jump_back.
218
+ fn emit_jump_back(&mut self) -> usize {
219
+ let pos = self.chunk.code.len();
220
+ self.chunk.write_u8(Opcode::JumpBack as u8);
221
+ self.chunk.write_u16(0);
222
+ pos + 1
223
+ }
224
+
225
+ fn patch_jump(&mut self, patch_pos: usize, target: usize) {
226
+ let base = patch_pos + 2;
227
+ let jump_offset = (target as i32).wrapping_sub(base as i32);
228
+ let bytes = (jump_offset as i16).to_be_bytes();
229
+ self.chunk.code[patch_pos] = bytes[0];
230
+ self.chunk.code[patch_pos + 1] = bytes[1];
231
+ }
232
+
233
+ /// Patch a JumpBack operand: distance from the IP after this insn back to `target`.
234
+ /// `patch_pos` is the first byte of the u16 operand (same as [`Self::emit_jump_back`]'s return value).
235
+ fn patch_jump_back(&mut self, patch_pos: usize, target: usize) {
236
+ let after_insn = patch_pos + 2;
237
+ let dist = after_insn.saturating_sub(target);
238
+ let bytes = (dist as u16).to_be_bytes();
239
+ self.chunk.code[patch_pos] = bytes[0];
240
+ self.chunk.code[patch_pos + 1] = bytes[1];
241
+ }
242
+
243
+ /// Detect property-based numeric sort: (a, b) => a.prop - b.prop or (a, b) => b.prop - a.prop.
244
+ /// Returns Some((prop_name, asc)) or None.
245
+ fn detect_property_sort_comparator(expr: &Expr) -> Option<(Arc<str>, bool)> {
246
+ if let Expr::ArrowFunction { params, body, .. } = expr {
247
+ if params.len() != 2 {
248
+ return None;
249
+ }
250
+ let (param_a, param_b) = match (&params[0], &params[1]) {
251
+ (FunParam::Simple(a), FunParam::Simple(b))
252
+ if a.default.is_none() && b.default.is_none() =>
253
+ {
254
+ (a.name.as_ref(), b.name.as_ref())
255
+ }
256
+ _ => return None,
257
+ };
258
+ let body_expr = match body {
259
+ ArrowBody::Expr(e) => e.as_ref(),
260
+ ArrowBody::Block(stmt) => {
261
+ if let Statement::ExprStmt { expr: e, .. } = stmt.as_ref() {
262
+ e
263
+ } else {
264
+ return None;
265
+ }
266
+ }
267
+ };
268
+ if let Expr::Binary {
269
+ left,
270
+ op: BinOp::Sub,
271
+ right,
272
+ ..
273
+ } = body_expr
274
+ {
275
+ if let (
276
+ Expr::Member {
277
+ object: lo,
278
+ prop: MemberProp::Name { name: p, .. },
279
+ ..
280
+ },
281
+ Expr::Member {
282
+ object: ro,
283
+ prop: MemberProp::Name { name: pr, .. },
284
+ ..
285
+ },
286
+ ) = (left.as_ref(), right.as_ref())
287
+ {
288
+ if p != pr {
289
+ return None;
290
+ }
291
+ if let (Expr::Ident { name: ln, .. }, Expr::Ident { name: rn, .. }) =
292
+ (lo.as_ref(), ro.as_ref())
293
+ {
294
+ if ln.as_ref() == param_a && rn.as_ref() == param_b {
295
+ return Some((Arc::clone(p), true));
296
+ }
297
+ if ln.as_ref() == param_b && rn.as_ref() == param_a {
298
+ return Some((Arc::clone(p), false));
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ None
305
+ }
306
+
307
+ /// Detect numeric sort comparator: (a, b) => a - b (asc) or (a, b) => b - a (desc).
308
+ fn detect_numeric_sort_comparator(expr: &Expr) -> Option<bool> {
309
+ if let Expr::ArrowFunction { params, body, .. } = expr {
310
+ if params.len() != 2 {
311
+ return None;
312
+ }
313
+ let (param_a, param_b) = match (&params[0], &params[1]) {
314
+ (FunParam::Simple(a), FunParam::Simple(b))
315
+ if a.default.is_none() && b.default.is_none() =>
316
+ {
317
+ (a.name.as_ref(), b.name.as_ref())
318
+ }
319
+ _ => return None,
320
+ };
321
+ let body_expr = match body {
322
+ ArrowBody::Expr(e) => e.as_ref(),
323
+ ArrowBody::Block(stmt) => {
324
+ if let Statement::ExprStmt { expr: e, .. } = stmt.as_ref() {
325
+ e
326
+ } else {
327
+ return None;
328
+ }
329
+ }
330
+ };
331
+ if let Expr::Binary {
332
+ left,
333
+ op: BinOp::Sub,
334
+ right,
335
+ ..
336
+ } = body_expr
337
+ {
338
+ if let (
339
+ Expr::Ident {
340
+ name: left_name, ..
341
+ },
342
+ Expr::Ident {
343
+ name: right_name, ..
344
+ },
345
+ ) = (left.as_ref(), right.as_ref())
346
+ {
347
+ if left_name.as_ref() == param_a && right_name.as_ref() == param_b {
348
+ return Some(true);
349
+ }
350
+ if left_name.as_ref() == param_b && right_name.as_ref() == param_a {
351
+ return Some(false);
352
+ }
353
+ }
354
+ }
355
+ }
356
+ None
357
+ }
358
+
359
+ /// Detect simple map callback: x => x (identity) or x => x op const / x => const op x.
360
+ /// Returns SimpleMapResult for map optimization.
361
+ fn detect_simple_map_callback(expr: &Expr) -> Option<SimpleMapResult> {
362
+ let (params, body) = match expr {
363
+ Expr::ArrowFunction { params, body, .. } => (params, body),
364
+ _ => return None,
365
+ };
366
+ if params.len() != 1 {
367
+ return None;
368
+ }
369
+ let param_name = match &params[0] {
370
+ FunParam::Simple(tp) if tp.default.is_none() => tp.name.as_ref(),
371
+ _ => return None,
372
+ };
373
+ let expr_ref: &Expr = match body {
374
+ ArrowBody::Expr(e) => e.as_ref(),
375
+ ArrowBody::Block(stmt) => {
376
+ let s = stmt.as_ref();
377
+ if let Statement::Return {
378
+ value: Some(ref e), ..
379
+ } = s
380
+ {
381
+ e
382
+ } else if let Statement::ExprStmt { expr: ref e, .. } = s {
383
+ e
384
+ } else {
385
+ return None;
386
+ }
387
+ }
388
+ };
389
+ // Identity: x => x
390
+ if let Expr::Ident { name, .. } = expr_ref {
391
+ if name.as_ref() == param_name {
392
+ return Some(SimpleMapResult::Identity);
393
+ }
394
+ }
395
+ // Binary: x op const or const op x
396
+ if let Expr::Binary {
397
+ left, op, right, ..
398
+ } = expr_ref
399
+ {
400
+ let left_is_param =
401
+ matches!(left.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
402
+ let right_is_param =
403
+ matches!(right.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
404
+ let left_is_literal = matches!(left.as_ref(), Expr::Literal { .. });
405
+ let right_is_literal = matches!(right.as_ref(), Expr::Literal { .. });
406
+ if left_is_param && right_is_literal {
407
+ if let Some(c) = literal_to_constant(right.as_ref()) {
408
+ return Some(SimpleMapResult::BinOp(*op, c, true));
409
+ }
410
+ }
411
+ if left_is_literal && right_is_param {
412
+ if let Some(c) = literal_to_constant(left.as_ref()) {
413
+ return Some(SimpleMapResult::BinOp(*op, c, false));
414
+ }
415
+ }
416
+ }
417
+ None
418
+ }
419
+
420
+ /// Detect simple filter callback: x => x op const or x => const op x (comparison that returns bool).
421
+ fn detect_simple_filter_callback(expr: &Expr) -> Option<(BinOp, Constant, bool)> {
422
+ let (params, body) = match expr {
423
+ Expr::ArrowFunction { params, body, .. } => (params, body),
424
+ _ => return None,
425
+ };
426
+ if params.len() != 1 {
427
+ return None;
428
+ }
429
+ let param_name = match &params[0] {
430
+ FunParam::Simple(tp) if tp.default.is_none() => tp.name.as_ref(),
431
+ _ => return None,
432
+ };
433
+ let expr_ref: &Expr = match body {
434
+ ArrowBody::Expr(e) => e.as_ref(),
435
+ ArrowBody::Block(stmt) => {
436
+ let s = stmt.as_ref();
437
+ if let Statement::Return {
438
+ value: Some(ref e), ..
439
+ } = s
440
+ {
441
+ e
442
+ } else if let Statement::ExprStmt { expr: ref e, .. } = s {
443
+ e
444
+ } else {
445
+ return None;
446
+ }
447
+ }
448
+ };
449
+ if let Expr::Binary {
450
+ left, op, right, ..
451
+ } = expr_ref
452
+ {
453
+ if !matches!(
454
+ op,
455
+ BinOp::Eq
456
+ | BinOp::Ne
457
+ | BinOp::StrictEq
458
+ | BinOp::StrictNe
459
+ | BinOp::Lt
460
+ | BinOp::Le
461
+ | BinOp::Gt
462
+ | BinOp::Ge
463
+ | BinOp::And
464
+ | BinOp::Or
465
+ ) {
466
+ return None;
467
+ }
468
+ let left_is_param =
469
+ matches!(left.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
470
+ let right_is_param =
471
+ matches!(right.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
472
+ let left_is_literal = matches!(left.as_ref(), Expr::Literal { .. });
473
+ let right_is_literal = matches!(right.as_ref(), Expr::Literal { .. });
474
+ if left_is_param && right_is_literal {
475
+ if let Some(c) = literal_to_constant(right.as_ref()) {
476
+ return Some((*op, c, true));
477
+ }
478
+ }
479
+ if left_is_literal && right_is_param {
480
+ if let Some(c) = literal_to_constant(left.as_ref()) {
481
+ return Some((*op, c, false));
482
+ }
483
+ }
484
+ }
485
+ None
486
+ }
487
+
488
+ fn compile_program(&mut self, program: &Program) -> Result<(), CompileError> {
489
+ let stmts = &program.statements;
490
+ let last_is_expr = self.retain_last_expr
491
+ && stmts
492
+ .last()
493
+ .map(|s| matches!(s, Statement::ExprStmt { .. }))
494
+ .unwrap_or(false);
495
+
496
+ if last_is_expr {
497
+ let (rest, last) = stmts.split_at(stmts.len().saturating_sub(1));
498
+ for stmt in rest {
499
+ self.compile_statement(stmt)?;
500
+ }
501
+ if let Some(Statement::ExprStmt { expr, .. }) = last.first() {
502
+ self.compile_expr(expr)?;
503
+ }
504
+ } else {
505
+ for stmt in stmts {
506
+ self.compile_statement(stmt)?;
507
+ }
508
+ let idx = self.constant_idx(Constant::Null);
509
+ self.emit(Opcode::LoadConst);
510
+ self.chunk.write_u16(idx);
511
+ }
512
+ Ok(())
513
+ }
514
+
515
+ fn compile_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
516
+ match stmt {
517
+ Statement::Block { statements, .. } => {
518
+ self.emit(Opcode::EnterBlock);
519
+ self.block_depth += 1;
520
+ self.scope.push(HashMap::new());
521
+ for s in statements {
522
+ self.compile_statement(s)?;
523
+ }
524
+ self.scope.pop();
525
+ self.emit(Opcode::ExitBlock);
526
+ self.block_depth -= 1;
527
+ }
528
+ Statement::VarDecl {
529
+ name,
530
+ init,
531
+ mutable: _,
532
+ ..
533
+ } => {
534
+ if let Some(expr) = init {
535
+ self.compile_expr(expr)?;
536
+ } else {
537
+ let idx = self.constant_idx(Constant::Null);
538
+ self.emit(Opcode::LoadConst);
539
+ self.chunk.write_u16(idx);
540
+ }
541
+ let idx = self.name_idx(name);
542
+ self.emit_u16(Opcode::DeclareVar, idx);
543
+ self.scope
544
+ .last_mut()
545
+ .unwrap()
546
+ .insert(Arc::clone(name), false);
547
+ }
548
+ Statement::VarDeclDestructure { pattern, init, .. } => {
549
+ self.compile_expr(init)?;
550
+ self.compile_destructure(pattern, false, false)?;
551
+ }
552
+ Statement::ExprStmt { expr, .. } => {
553
+ self.compile_expr(expr)?;
554
+ self.emit(Opcode::Pop);
555
+ }
556
+ Statement::If {
557
+ cond,
558
+ then_branch,
559
+ else_branch,
560
+ ..
561
+ } => {
562
+ self.compile_expr(cond)?;
563
+ let jump_else = self.emit_jump(Opcode::JumpIfFalse);
564
+ self.compile_statement(then_branch)?;
565
+ let jump_end = self.emit_jump(Opcode::Jump);
566
+ self.patch_jump(jump_else, self.chunk.code.len());
567
+ if let Some(else_s) = else_branch {
568
+ self.compile_statement(else_s)?;
569
+ }
570
+ self.patch_jump(jump_end, self.chunk.code.len());
571
+ }
572
+ Statement::While { cond, body, .. } => {
573
+ let start = self.chunk.code.len();
574
+ self.loop_stack.push(LoopInfo {
575
+ break_patches: Vec::new(),
576
+ continue_patches: Vec::new(),
577
+ continue_is_forward_jump: false,
578
+ });
579
+ self.breakable_stack.push(Breakable::Loop {
580
+ unwind_depth: self.block_depth,
581
+ });
582
+ self.compile_expr(cond)?;
583
+ let jump_out = self.emit_jump(Opcode::JumpIfFalse);
584
+ // JumpIfFalse already pops condition when taking body
585
+ self.compile_statement(body)?;
586
+ let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(start);
587
+ self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
588
+ let end = self.chunk.code.len();
589
+ self.patch_jump(jump_out, end);
590
+ let info = self.loop_stack.pop().unwrap();
591
+ self.breakable_stack.pop();
592
+ for p in info.continue_patches {
593
+ self.patch_jump_back(p, start);
594
+ }
595
+ for p in info.break_patches {
596
+ self.patch_jump(p, end);
597
+ }
598
+ }
599
+ Statement::For {
600
+ init,
601
+ cond,
602
+ update,
603
+ body,
604
+ ..
605
+ } => {
606
+ self.scope.push(HashMap::new());
607
+ if let Some(i) = init {
608
+ self.compile_for_init_statement(i.as_ref())?;
609
+ }
610
+ let cond_start = self.chunk.code.len();
611
+ if let Some(c) = cond {
612
+ self.compile_expr(c)?;
613
+ } else {
614
+ let idx = self.constant_idx(Constant::Bool(true));
615
+ self.emit(Opcode::LoadConst);
616
+ self.chunk.write_u16(idx);
617
+ }
618
+ let jump_out = self.emit_jump(Opcode::JumpIfFalse);
619
+ self.loop_stack.push(LoopInfo {
620
+ break_patches: Vec::new(),
621
+ continue_patches: Vec::new(),
622
+ continue_is_forward_jump: true,
623
+ });
624
+ self.breakable_stack.push(Breakable::Loop {
625
+ unwind_depth: self.block_depth,
626
+ });
627
+ self.compile_statement(body)?;
628
+ let update_start = self.chunk.code.len();
629
+ if let Some(u) = update {
630
+ self.compile_expr(u)?;
631
+ self.emit(Opcode::Pop);
632
+ }
633
+ let info = self.loop_stack.pop().unwrap();
634
+ for p in info.continue_patches {
635
+ self.patch_jump(p, update_start);
636
+ }
637
+ let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(cond_start);
638
+ self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
639
+ let end = self.chunk.code.len();
640
+ self.patch_jump(jump_out, end);
641
+ for p in info.break_patches {
642
+ self.patch_jump(p, end);
643
+ }
644
+ self.breakable_stack.pop();
645
+ self.scope.pop();
646
+ }
647
+ Statement::ForOf {
648
+ name,
649
+ iterable,
650
+ body,
651
+ ..
652
+ } => {
653
+ self.compile_expr(iterable)?;
654
+ self.scope.push(HashMap::new());
655
+ let arr_name = Arc::from("__forof_arr__");
656
+ let i_name = Arc::from("__forof_i__");
657
+ let len_name = Arc::from("__forof_len__");
658
+ let arr_idx = self.name_idx(&arr_name);
659
+ let i_idx = self.name_idx(&i_name);
660
+ let len_idx = self.name_idx(&len_name);
661
+ let name_idx = self.name_idx(name);
662
+ self.emit_u16(Opcode::DeclareVar, arr_idx);
663
+ self.scope
664
+ .last_mut()
665
+ .unwrap()
666
+ .insert(arr_name.clone(), false);
667
+ self.emit_u16(Opcode::LoadVar, arr_idx);
668
+ let len_name_idx = self.name_idx(&Arc::from("length"));
669
+ self.emit_u16(Opcode::GetMember, len_name_idx);
670
+ self.emit_u16(Opcode::DeclareVar, len_idx);
671
+ self.scope
672
+ .last_mut()
673
+ .unwrap()
674
+ .insert(len_name.clone(), false);
675
+ let zero_idx = self.constant_idx(Constant::Number(0.0));
676
+ self.emit(Opcode::LoadConst);
677
+ self.chunk.write_u16(zero_idx);
678
+ self.emit_u16(Opcode::DeclareVar, i_idx);
679
+ self.scope.last_mut().unwrap().insert(i_name.clone(), false);
680
+ let loop_start = self.chunk.code.len();
681
+ self.loop_stack.push(LoopInfo {
682
+ break_patches: Vec::new(),
683
+ continue_patches: Vec::new(),
684
+ continue_is_forward_jump: false,
685
+ });
686
+ self.breakable_stack.push(Breakable::Loop {
687
+ unwind_depth: self.block_depth,
688
+ });
689
+ self.emit_u16(Opcode::LoadVar, arr_idx);
690
+ self.emit_u16(Opcode::LoadVar, i_idx);
691
+ self.emit(Opcode::GetIndex);
692
+ self.emit_u16(Opcode::DeclareVar, name_idx);
693
+ self.scope
694
+ .last_mut()
695
+ .unwrap()
696
+ .insert(Arc::clone(name), false);
697
+ self.compile_statement(body)?;
698
+ self.emit_u16(Opcode::LoadVar, i_idx);
699
+ let one_idx = self.constant_idx(Constant::Number(1.0));
700
+ self.emit(Opcode::LoadConst);
701
+ self.chunk.write_u16(one_idx);
702
+ self.emit_u8(Opcode::BinOp, 0);
703
+ self.emit_u16(Opcode::StoreVar, i_idx);
704
+ self.emit_u16(Opcode::LoadVar, i_idx);
705
+ self.emit_u16(Opcode::LoadVar, len_idx);
706
+ self.emit_u8(Opcode::BinOp, 10);
707
+ let jump_out = self.emit_jump(Opcode::JumpIfFalse);
708
+ let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(loop_start);
709
+ self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
710
+ let end = self.chunk.code.len();
711
+ self.patch_jump(jump_out, end);
712
+ let info = self.loop_stack.pop().unwrap();
713
+ self.breakable_stack.pop();
714
+ for p in info.continue_patches {
715
+ self.patch_jump_back(p, loop_start);
716
+ }
717
+ for p in info.break_patches {
718
+ self.patch_jump(p, end);
719
+ }
720
+ self.scope.pop();
721
+ }
722
+ Statement::Return { value, .. } => {
723
+ if let Some(v) = value {
724
+ self.compile_expr(v)?;
725
+ } else {
726
+ let idx = self.constant_idx(Constant::Null);
727
+ self.emit(Opcode::LoadConst);
728
+ self.chunk.write_u16(idx);
729
+ }
730
+ self.emit(Opcode::Return);
731
+ }
732
+ Statement::Break { .. } => {
733
+ let unwind_depth = match self.breakable_stack.last() {
734
+ Some(Breakable::Loop { unwind_depth })
735
+ | Some(Breakable::Switch { unwind_depth }) => *unwind_depth,
736
+ None => {
737
+ return Err(CompileError {
738
+ message: "break not inside a loop or switch".to_string(),
739
+ });
740
+ }
741
+ };
742
+ self.emit_exit_blocks_until_depth(unwind_depth);
743
+ let pos = self.emit_jump(Opcode::Jump);
744
+ match self.breakable_stack.last() {
745
+ Some(Breakable::Loop { .. }) => {
746
+ self.loop_stack.last_mut().unwrap().break_patches.push(pos);
747
+ }
748
+ Some(Breakable::Switch { .. }) => {
749
+ self.switch_stack
750
+ .last_mut()
751
+ .unwrap()
752
+ .break_patches
753
+ .push(pos);
754
+ }
755
+ None => {}
756
+ }
757
+ }
758
+ Statement::Continue { .. } => {
759
+ let unwind_depth = self
760
+ .breakable_stack
761
+ .iter()
762
+ .rev()
763
+ .find_map(|b| match b {
764
+ Breakable::Loop { unwind_depth } => Some(*unwind_depth),
765
+ Breakable::Switch { .. } => None,
766
+ })
767
+ .ok_or_else(|| CompileError {
768
+ message: "continue not inside a loop".to_string(),
769
+ })?;
770
+ self.emit_exit_blocks_until_depth(unwind_depth);
771
+ let forward = self
772
+ .loop_stack
773
+ .last()
774
+ .expect("continue not inside a loop")
775
+ .continue_is_forward_jump;
776
+ let pos = if forward {
777
+ self.emit_jump(Opcode::Jump)
778
+ } else {
779
+ self.emit_jump_back()
780
+ };
781
+ self.loop_stack
782
+ .last_mut()
783
+ .expect("continue not inside a loop")
784
+ .continue_patches
785
+ .push(pos);
786
+ }
787
+ Statement::FunDecl {
788
+ name,
789
+ params,
790
+ body,
791
+ rest_param,
792
+ async_: _,
793
+ ..
794
+ } => {
795
+ let formal_len = params.len();
796
+ let (mut param_names, slots) = Self::plan_function_params(params)?;
797
+ let mut inner = Chunk::new();
798
+ if let Some(rp) = rest_param {
799
+ param_names.push(Arc::clone(&rp.name));
800
+ inner.rest_param_index = (param_names.len() as u16).saturating_sub(1);
801
+ }
802
+ for p in &param_names {
803
+ inner.add_name(Arc::clone(p));
804
+ }
805
+ inner.param_count = param_names.len() as u16;
806
+ let mut inner_comp = Compiler::new(&mut inner, false);
807
+ inner_comp.scope = vec![param_names
808
+ .iter()
809
+ .map(|n| (Arc::clone(n), false))
810
+ .collect::<HashMap<_, _>>()];
811
+ inner_comp.emit_param_destructure_prologue(&param_names[..formal_len], &slots)?;
812
+ inner_comp.compile_statement(body)?;
813
+ inner_comp.emit(Opcode::LoadConst);
814
+ let idx = inner_comp.constant_idx(Constant::Null);
815
+ inner_comp.chunk.write_u16(idx);
816
+ inner_comp.emit(Opcode::Return);
817
+ let nested_idx = self.chunk.add_nested(inner);
818
+ self.emit(Opcode::LoadConst);
819
+ let idx = self.constant_idx(Constant::Closure(nested_idx));
820
+ self.chunk.write_u16(idx);
821
+ let idx = self.name_idx(name);
822
+ self.emit_u16(Opcode::DeclareVar, idx);
823
+ self.scope
824
+ .last_mut()
825
+ .unwrap()
826
+ .insert(Arc::clone(name), false);
827
+ }
828
+ Statement::DoWhile { body, cond, .. } => {
829
+ let start = self.chunk.code.len();
830
+ self.loop_stack.push(LoopInfo {
831
+ break_patches: Vec::new(),
832
+ continue_patches: Vec::new(),
833
+ continue_is_forward_jump: false,
834
+ });
835
+ self.breakable_stack.push(Breakable::Loop {
836
+ unwind_depth: self.block_depth,
837
+ });
838
+ self.compile_statement(body)?;
839
+ let cond_start = self.chunk.code.len();
840
+ self.compile_expr(cond)?;
841
+ let jump_back = self.emit_jump(Opcode::JumpIfFalse);
842
+ let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(start);
843
+ self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
844
+ let end = self.chunk.code.len();
845
+ self.patch_jump(jump_back, end);
846
+ let info = self.loop_stack.pop().unwrap();
847
+ self.breakable_stack.pop();
848
+ for p in info.continue_patches {
849
+ self.patch_jump_back(p, cond_start);
850
+ }
851
+ for p in info.break_patches {
852
+ self.patch_jump(p, end);
853
+ }
854
+ }
855
+ Statement::Switch {
856
+ expr,
857
+ cases,
858
+ default_body,
859
+ ..
860
+ } => {
861
+ let switch_unwind_depth = self.block_depth;
862
+ self.switch_stack.push(SwitchInfo {
863
+ break_patches: Vec::new(),
864
+ });
865
+ self.breakable_stack.push(Breakable::Switch {
866
+ unwind_depth: switch_unwind_depth,
867
+ });
868
+ self.compile_expr(expr)?;
869
+ self.emit(Opcode::Dup);
870
+ let mut end_patches = Vec::new();
871
+ for (case_expr, case_body) in cases {
872
+ self.emit(Opcode::Dup);
873
+ if let Some(ce) = case_expr {
874
+ self.compile_expr(ce)?;
875
+ self.emit_u8(Opcode::BinOp, 8);
876
+ let jump_next = self.emit_jump(Opcode::JumpIfFalse);
877
+ // JumpIfFalse already pops the match result when taking this case
878
+ self.compile_statement(&Statement::Block {
879
+ statements: case_body.clone(),
880
+ span: Span {
881
+ start: (0, 0),
882
+ end: (0, 0),
883
+ },
884
+ })?;
885
+ let jump_end = self.emit_jump(Opcode::Jump);
886
+ end_patches.push(jump_end);
887
+ self.patch_jump(jump_next, self.chunk.code.len());
888
+ } else {
889
+ self.emit(Opcode::Pop);
890
+ self.compile_statement(&Statement::Block {
891
+ statements: case_body.clone(),
892
+ span: Span {
893
+ start: (0, 0),
894
+ end: (0, 0),
895
+ },
896
+ })?;
897
+ }
898
+ }
899
+ if let Some(body) = default_body {
900
+ self.emit(Opcode::Pop);
901
+ self.compile_statement(&Statement::Block {
902
+ statements: body.clone(),
903
+ span: Span {
904
+ start: (0, 0),
905
+ end: (0, 0),
906
+ },
907
+ })?;
908
+ } else {
909
+ self.emit(Opcode::Pop);
910
+ }
911
+ for p in end_patches {
912
+ self.patch_jump(p, self.chunk.code.len());
913
+ }
914
+ let sw = self.switch_stack.pop().unwrap();
915
+ self.breakable_stack.pop();
916
+ for p in sw.break_patches {
917
+ self.patch_jump(p, self.chunk.code.len());
918
+ }
919
+ }
920
+ Statement::Throw { value, .. } => {
921
+ self.compile_expr(value)?;
922
+ self.emit(Opcode::Throw);
923
+ }
924
+ Statement::Try {
925
+ body,
926
+ catch_param,
927
+ catch_body,
928
+ finally_body,
929
+ ..
930
+ } => {
931
+ let catch_offset_pos = self.chunk.code.len();
932
+ self.emit(Opcode::EnterTry);
933
+ self.chunk.write_u16(0);
934
+ self.compile_statement(body)?;
935
+ self.emit(Opcode::ExitTry);
936
+ let jump_over_catch = self.emit_jump(Opcode::Jump);
937
+ let catch_start = self.chunk.code.len();
938
+ if let Some(catch_stmt) = catch_body {
939
+ if let Some(param) = catch_param {
940
+ self.emit(Opcode::EnterBlock);
941
+ self.block_depth += 1;
942
+ self.scope.push(HashMap::new());
943
+ let param_idx = self.name_idx(param);
944
+ self.emit_u16(Opcode::DeclareVar, param_idx);
945
+ self.scope
946
+ .last_mut()
947
+ .unwrap()
948
+ .insert(Arc::clone(param), false);
949
+ self.compile_statement(catch_stmt)?;
950
+ self.scope.pop();
951
+ self.emit(Opcode::ExitBlock);
952
+ self.block_depth -= 1;
953
+ } else {
954
+ self.emit(Opcode::Pop);
955
+ self.compile_statement(catch_stmt)?;
956
+ }
957
+ } else {
958
+ self.emit(Opcode::Throw);
959
+ }
960
+ let after_catch = self.chunk.code.len();
961
+ self.patch_jump(jump_over_catch, after_catch);
962
+ if let Some(finally) = finally_body {
963
+ self.compile_statement(finally)?;
964
+ }
965
+ let catch_offset =
966
+ catch_start.wrapping_sub(catch_offset_pos).wrapping_sub(3) as u16;
967
+ self.chunk.code[catch_offset_pos + 1] = (catch_offset >> 8) as u8;
968
+ self.chunk.code[catch_offset_pos + 2] = (catch_offset & 0xff) as u8;
969
+ }
970
+ Statement::Import { .. } => {
971
+ return Err(CompileError {
972
+ message: "Import not supported in bytecode".to_string(),
973
+ });
974
+ }
975
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
976
+ ExportDeclaration::Named(inner_stmt) => {
977
+ self.compile_statement(inner_stmt.as_ref())?;
978
+ }
979
+ ExportDeclaration::Default(_) => {
980
+ return Err(CompileError {
981
+ message: "export default is not supported in bytecode".to_string(),
982
+ });
983
+ }
984
+ },
985
+ Statement::TypeAlias { .. }
986
+ | Statement::DeclareVar { .. }
987
+ | Statement::DeclareFun { .. } => {}
988
+ }
989
+ Ok(())
990
+ }
991
+
992
+ fn compile_destructure(
993
+ &mut self,
994
+ pattern: &DestructPattern,
995
+ mutable: bool,
996
+ for_header_binding: bool,
997
+ ) -> Result<(), CompileError> {
998
+ let decl_op = if for_header_binding {
999
+ Opcode::DeclareVarPlain
1000
+ } else {
1001
+ Opcode::DeclareVar
1002
+ };
1003
+ match pattern {
1004
+ DestructPattern::Array(elements) => {
1005
+ for (i, elem) in elements.iter().enumerate() {
1006
+ match elem {
1007
+ Some(DestructElement::Ident(name, _)) => {
1008
+ self.emit(Opcode::Dup);
1009
+ let idx = self.constant_idx(Constant::Number(i as f64));
1010
+ self.emit(Opcode::LoadConst);
1011
+ self.chunk.write_u16(idx);
1012
+ self.emit(Opcode::GetIndex);
1013
+ let idx = self.name_idx(name);
1014
+ self.emit_u16(decl_op, idx);
1015
+ self.scope
1016
+ .last_mut()
1017
+ .unwrap()
1018
+ .insert(Arc::clone(name), false);
1019
+ }
1020
+ _ => {
1021
+ return Err(CompileError {
1022
+ message: "Complex destructuring not yet supported".to_string(),
1023
+ });
1024
+ }
1025
+ }
1026
+ }
1027
+ self.emit(Opcode::Pop);
1028
+ }
1029
+ DestructPattern::Object(props) => {
1030
+ for prop in props {
1031
+ self.emit(Opcode::Dup);
1032
+ let key_idx = self.constant_idx(Constant::String(Arc::clone(&prop.key)));
1033
+ self.emit(Opcode::LoadConst);
1034
+ self.chunk.write_u16(key_idx);
1035
+ self.emit(Opcode::GetIndex); // GetIndex pops obj, index and uses get_member
1036
+ match &prop.value {
1037
+ DestructElement::Ident(name, _) => {
1038
+ let idx = self.name_idx(name);
1039
+ self.emit_u16(decl_op, idx);
1040
+ if mutable {
1041
+ self.scope
1042
+ .last_mut()
1043
+ .unwrap()
1044
+ .insert(Arc::clone(name), false);
1045
+ }
1046
+ }
1047
+ _ => {
1048
+ return Err(CompileError {
1049
+ message: "Nested object destructuring not yet supported"
1050
+ .to_string(),
1051
+ });
1052
+ }
1053
+ }
1054
+ }
1055
+ self.emit(Opcode::Pop);
1056
+ }
1057
+ }
1058
+ Ok(())
1059
+ }
1060
+
1061
+ fn compile_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
1062
+ match expr {
1063
+ Expr::Literal { value, .. } => {
1064
+ let c = match value {
1065
+ Literal::Number(n) => Constant::Number(*n),
1066
+ Literal::String(s) => Constant::String(Arc::clone(s)),
1067
+ Literal::Bool(b) => Constant::Bool(*b),
1068
+ Literal::Null => Constant::Null,
1069
+ };
1070
+ let idx = self.constant_idx(c);
1071
+ self.emit(Opcode::LoadConst);
1072
+ self.chunk.write_u16(idx);
1073
+ }
1074
+ Expr::Ident { name, .. } => {
1075
+ let idx = self.name_idx(name);
1076
+ self.emit_u16(Opcode::LoadVar, idx);
1077
+ }
1078
+ Expr::Binary {
1079
+ left, op, right, ..
1080
+ } => {
1081
+ match op {
1082
+ BinOp::And => {
1083
+ // Short-circuit: a && b => if !a then a else b
1084
+ self.compile_expr(left)?;
1085
+ self.emit(Opcode::Dup);
1086
+ let jump_shortcut = self.emit_jump(Opcode::JumpIfFalse);
1087
+ self.compile_expr(right)?; // left still on stack from Dup
1088
+ self.emit_u8(Opcode::BinOp, binop_to_u8(BinOp::And));
1089
+ let jump_end = self.emit_jump(Opcode::Jump);
1090
+ self.patch_jump(jump_shortcut, self.chunk.code.len());
1091
+ self.patch_jump(jump_end, self.chunk.code.len());
1092
+ }
1093
+ BinOp::Or => {
1094
+ // Short-circuit: a || b => if a then a else b
1095
+ self.compile_expr(left)?;
1096
+ self.emit(Opcode::Dup);
1097
+ let jump_eval_right = self.emit_jump(Opcode::JumpIfFalse);
1098
+ let jump_end = self.emit_jump(Opcode::Jump);
1099
+ self.patch_jump(jump_eval_right, self.chunk.code.len());
1100
+ self.emit(Opcode::Pop); // discard falsy left
1101
+ self.compile_expr(right)?;
1102
+ self.patch_jump(jump_end, self.chunk.code.len());
1103
+ }
1104
+ _ => {
1105
+ self.compile_expr(left)?;
1106
+ self.compile_expr(right)?;
1107
+ self.emit_u8(Opcode::BinOp, binop_to_u8(*op));
1108
+ }
1109
+ }
1110
+ }
1111
+ Expr::Unary { op, operand, .. } => {
1112
+ self.compile_expr(operand)?;
1113
+ self.emit_u8(Opcode::UnaryOp, unaryop_to_u8(*op));
1114
+ }
1115
+ Expr::Call { callee, args, .. } => {
1116
+ // Fast path: arr.sort((a,b)=>a-b) or arr.sort((a,b)=>b-a) -> ArraySortNumeric
1117
+ if !args.iter().any(|a| matches!(a, CallArg::Spread(_)))
1118
+ && args.len() == 1
1119
+ && matches!(args[0], CallArg::Expr(_))
1120
+ {
1121
+ if let (
1122
+ Expr::Member {
1123
+ object,
1124
+ prop: MemberProp::Name { name: key, .. },
1125
+ optional: false,
1126
+ ..
1127
+ },
1128
+ CallArg::Expr(cmp_expr),
1129
+ ) = (callee.as_ref(), &args[0])
1130
+ {
1131
+ if key.as_ref() == "sort" {
1132
+ if let Some(ascending) = Self::detect_numeric_sort_comparator(cmp_expr)
1133
+ {
1134
+ self.compile_expr(object)?;
1135
+ self.emit_u8(
1136
+ Opcode::ArraySortNumeric,
1137
+ if ascending { 0 } else { 1 },
1138
+ );
1139
+ return Ok(());
1140
+ }
1141
+ if let Some((prop, ascending)) =
1142
+ Self::detect_property_sort_comparator(cmp_expr)
1143
+ {
1144
+ self.compile_expr(object)?;
1145
+ let prop_idx = self.constant_idx(Constant::String(prop));
1146
+ self.emit(Opcode::ArraySortByProperty);
1147
+ self.chunk.write_u16(prop_idx);
1148
+ self.chunk.write_u16(if ascending { 0 } else { 1 });
1149
+ return Ok(());
1150
+ }
1151
+ }
1152
+ if key.as_ref() == "map" {
1153
+ if let Some(simple) = Self::detect_simple_map_callback(cmp_expr) {
1154
+ self.compile_expr(object)?;
1155
+ match simple {
1156
+ SimpleMapResult::Identity => {
1157
+ self.emit(Opcode::ArrayMapIdentity);
1158
+ }
1159
+ SimpleMapResult::BinOp(op, c, param_left) => {
1160
+ let const_idx = self.constant_idx(c);
1161
+ self.emit(Opcode::ArrayMapBinOp);
1162
+ self.chunk.write_u8(binop_to_u8(op));
1163
+ self.chunk.write_u16(const_idx);
1164
+ self.chunk.write_u8(if param_left { 0 } else { 1 });
1165
+ }
1166
+ }
1167
+ return Ok(());
1168
+ }
1169
+ }
1170
+ if key.as_ref() == "filter" {
1171
+ if let Some((op, const_val, param_left)) =
1172
+ Self::detect_simple_filter_callback(cmp_expr)
1173
+ {
1174
+ self.compile_expr(object)?;
1175
+ let const_idx = self.constant_idx(const_val);
1176
+ self.emit(Opcode::ArrayFilterBinOp);
1177
+ self.chunk.write_u8(binop_to_u8(op));
1178
+ self.chunk.write_u16(const_idx);
1179
+ self.chunk.write_u8(if param_left { 0 } else { 1 });
1180
+ return Ok(());
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1185
+ let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
1186
+ if has_spread {
1187
+ // Build args array [a, ...b, c], then callee, then CallSpread
1188
+ self.emit_u16(Opcode::NewArray, 0);
1189
+ for arg in args {
1190
+ match arg {
1191
+ CallArg::Expr(e) => {
1192
+ self.compile_expr(e)?;
1193
+ self.emit_u16(Opcode::NewArray, 1);
1194
+ self.emit(Opcode::ConcatArray);
1195
+ }
1196
+ CallArg::Spread(expr) => {
1197
+ self.compile_expr(expr)?;
1198
+ self.emit(Opcode::ConcatArray);
1199
+ }
1200
+ }
1201
+ }
1202
+ self.compile_expr(callee)?;
1203
+ self.emit(Opcode::CallSpread);
1204
+ } else {
1205
+ self.compile_expr(callee)?;
1206
+ for arg in args {
1207
+ if let CallArg::Expr(e) = arg {
1208
+ self.compile_expr(e)?;
1209
+ }
1210
+ }
1211
+ self.emit_u16(Opcode::Call, args.len() as u16);
1212
+ }
1213
+ }
1214
+ Expr::Member {
1215
+ object,
1216
+ prop,
1217
+ optional,
1218
+ ..
1219
+ } => {
1220
+ self.compile_expr(object)?;
1221
+ if *optional {
1222
+ self.emit(Opcode::Dup);
1223
+ let null_idx = self.constant_idx(Constant::Null);
1224
+ self.emit(Opcode::LoadConst);
1225
+ self.chunk.write_u16(null_idx);
1226
+ self.emit_u8(Opcode::BinOp, 8);
1227
+ let jump_to_null = self.emit_jump(Opcode::JumpIfFalse);
1228
+ let jump_to_get_instr = self.chunk.code.len();
1229
+ let jump_to_get = self.emit_jump(Opcode::Jump);
1230
+ self.patch_jump(jump_to_null, jump_to_get_instr);
1231
+ self.emit(Opcode::Pop);
1232
+ self.emit(Opcode::LoadConst);
1233
+ self.chunk.write_u16(null_idx);
1234
+ let jump_end = self.emit_jump(Opcode::Jump);
1235
+ self.patch_jump(jump_to_get, self.chunk.code.len());
1236
+ match prop {
1237
+ MemberProp::Name { name: key, .. } => {
1238
+ let idx = self.name_idx(key);
1239
+ self.emit_u16(Opcode::GetMemberOptional, idx);
1240
+ }
1241
+ MemberProp::Expr(e) => {
1242
+ self.compile_expr(e)?;
1243
+ self.emit(Opcode::GetIndex);
1244
+ }
1245
+ }
1246
+ self.patch_jump(jump_end, self.chunk.code.len());
1247
+ } else {
1248
+ match prop {
1249
+ MemberProp::Name { name: key, .. } => {
1250
+ let idx = self.name_idx(key);
1251
+ self.emit_u16(Opcode::GetMember, idx);
1252
+ }
1253
+ MemberProp::Expr(e) => {
1254
+ self.compile_expr(e)?;
1255
+ self.emit(Opcode::GetIndex);
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ Expr::Index { object, index, .. } => {
1261
+ self.compile_expr(object)?;
1262
+ self.compile_expr(index)?;
1263
+ self.emit(Opcode::GetIndex);
1264
+ }
1265
+ Expr::Conditional {
1266
+ cond,
1267
+ then_branch,
1268
+ else_branch,
1269
+ ..
1270
+ } => {
1271
+ self.compile_expr(cond)?;
1272
+ let jump_else = self.emit_jump(Opcode::JumpIfFalse);
1273
+ // JumpIfFalse pops condition when taking then; when taking else it also pops
1274
+ self.compile_expr(then_branch)?;
1275
+ let jump_end = self.emit_jump(Opcode::Jump);
1276
+ self.patch_jump(jump_else, self.chunk.code.len());
1277
+ // no Pop: condition was already popped by JumpIfFalse
1278
+ self.compile_expr(else_branch)?;
1279
+ self.patch_jump(jump_end, self.chunk.code.len());
1280
+ }
1281
+ Expr::NullishCoalesce { left, right, .. } => {
1282
+ self.compile_expr(left)?;
1283
+ self.emit(Opcode::Dup);
1284
+ let idx = self.constant_idx(Constant::Null);
1285
+ self.emit(Opcode::LoadConst);
1286
+ self.chunk.write_u16(idx);
1287
+ self.emit_u8(Opcode::BinOp, binop_to_u8(BinOp::StrictNe));
1288
+ let jump_to_right = self.emit_jump(Opcode::JumpIfFalse);
1289
+ let jump_end = self.emit_jump(Opcode::Jump);
1290
+ self.patch_jump(jump_to_right, self.chunk.code.len());
1291
+ self.emit(Opcode::Pop);
1292
+ self.compile_expr(right)?;
1293
+ self.patch_jump(jump_end, self.chunk.code.len());
1294
+ }
1295
+ Expr::Array { elements, .. } => {
1296
+ let has_spread = elements
1297
+ .iter()
1298
+ .any(|e| matches!(e, ArrayElement::Spread(_)));
1299
+ if has_spread {
1300
+ // Build array incrementally: start with [], concat each element
1301
+ self.emit_u16(Opcode::NewArray, 0);
1302
+ for elem in elements {
1303
+ match elem {
1304
+ ArrayElement::Expr(e) => {
1305
+ self.compile_expr(e)?;
1306
+ self.emit_u16(Opcode::NewArray, 1);
1307
+ self.emit(Opcode::ConcatArray);
1308
+ }
1309
+ ArrayElement::Spread(expr) => {
1310
+ self.compile_expr(expr)?;
1311
+ self.emit(Opcode::ConcatArray);
1312
+ }
1313
+ }
1314
+ }
1315
+ } else {
1316
+ for elem in elements {
1317
+ if let ArrayElement::Expr(e) = elem {
1318
+ self.compile_expr(e)?;
1319
+ }
1320
+ }
1321
+ self.emit_u16(Opcode::NewArray, elements.len() as u16);
1322
+ }
1323
+ }
1324
+ Expr::Object { props, .. } => {
1325
+ let has_spread = props.iter().any(|p| matches!(p, ObjectProp::Spread(_)));
1326
+ if has_spread {
1327
+ self.emit_u16(Opcode::NewObject, 0); // start with {}
1328
+ for prop in props {
1329
+ match prop {
1330
+ ObjectProp::KeyValue(k, v) => {
1331
+ let idx = self.constant_idx(Constant::String(Arc::clone(k)));
1332
+ self.emit(Opcode::LoadConst);
1333
+ self.chunk.write_u16(idx);
1334
+ self.compile_expr(v)?;
1335
+ self.emit_u16(Opcode::NewObject, 1);
1336
+ self.emit(Opcode::MergeObject);
1337
+ }
1338
+ ObjectProp::Spread(expr) => {
1339
+ self.compile_expr(expr)?;
1340
+ self.emit(Opcode::MergeObject);
1341
+ }
1342
+ }
1343
+ }
1344
+ } else {
1345
+ for prop in props {
1346
+ if let ObjectProp::KeyValue(k, v) = prop {
1347
+ let idx = self.constant_idx(Constant::String(Arc::clone(k)));
1348
+ self.emit(Opcode::LoadConst);
1349
+ self.chunk.write_u16(idx);
1350
+ self.compile_expr(v)?;
1351
+ }
1352
+ }
1353
+ self.emit_u16(Opcode::NewObject, props.len() as u16);
1354
+ }
1355
+ }
1356
+ Expr::Assign { name, value, .. } => {
1357
+ self.compile_expr(value)?;
1358
+ let idx = self.name_idx(name);
1359
+ self.emit_u16(Opcode::StoreVar, idx);
1360
+ self.emit_u16(Opcode::LoadVar, idx); // assign yields value
1361
+ }
1362
+ Expr::TypeOf { operand, .. } => {
1363
+ let typeof_idx = self.name_idx(&Arc::from("typeof"));
1364
+ self.emit_u16(Opcode::LoadGlobal, typeof_idx);
1365
+ self.compile_expr(operand)?;
1366
+ self.emit_u16(Opcode::Call, 1);
1367
+ }
1368
+ Expr::ArrowFunction { params, body, .. } => {
1369
+ let formal_len = params.len();
1370
+ let (param_names, slots) = Self::plan_function_params(params)?;
1371
+ let mut inner = Chunk::new();
1372
+ for p in &param_names {
1373
+ inner.add_name(Arc::clone(p));
1374
+ }
1375
+ inner.param_count = param_names.len() as u16;
1376
+ let mut inner_comp = Compiler::new(&mut inner, false);
1377
+ inner_comp.scope = vec![param_names
1378
+ .iter()
1379
+ .map(|n| (Arc::clone(n), false))
1380
+ .collect::<HashMap<_, _>>()];
1381
+ inner_comp.emit_param_destructure_prologue(&param_names[..formal_len], &slots)?;
1382
+ match body {
1383
+ ArrowBody::Expr(e) => {
1384
+ inner_comp.compile_expr(e)?;
1385
+ inner_comp.emit(Opcode::Return);
1386
+ }
1387
+ ArrowBody::Block(s) => {
1388
+ inner_comp.compile_statement(s)?;
1389
+ let idx = inner_comp.constant_idx(Constant::Null);
1390
+ inner_comp.emit(Opcode::LoadConst);
1391
+ inner_comp.chunk.write_u16(idx);
1392
+ inner_comp.emit(Opcode::Return);
1393
+ }
1394
+ }
1395
+ let nested_idx = self.chunk.add_nested(inner);
1396
+ let idx = self.constant_idx(Constant::Closure(nested_idx));
1397
+ self.emit(Opcode::LoadConst);
1398
+ self.chunk.write_u16(idx);
1399
+ }
1400
+ Expr::TemplateLiteral { quasis, exprs, .. } => {
1401
+ if exprs.is_empty() {
1402
+ let s = quasis[0].to_string();
1403
+ let idx = self.constant_idx(Constant::String(Arc::from(s)));
1404
+ self.emit(Opcode::LoadConst);
1405
+ self.chunk.write_u16(idx);
1406
+ } else {
1407
+ // Interleave quasis and exprs: quasi[0] + expr[0] + quasi[1] + expr[1] + ... + quasi[n]
1408
+ let first = quasis[0].to_string();
1409
+ let idx = self.constant_idx(Constant::String(Arc::from(first)));
1410
+ self.emit(Opcode::LoadConst);
1411
+ self.chunk.write_u16(idx);
1412
+ for (i, expr) in exprs.iter().enumerate() {
1413
+ self.compile_expr(expr)?;
1414
+ self.emit_u8(Opcode::BinOp, 0); // Add (string concat)
1415
+ let quasi_s = quasis[i + 1].to_string();
1416
+ let qidx = self.constant_idx(Constant::String(Arc::from(quasi_s)));
1417
+ self.emit(Opcode::LoadConst);
1418
+ self.chunk.write_u16(qidx);
1419
+ self.emit_u8(Opcode::BinOp, 0); // Add
1420
+ }
1421
+ }
1422
+ }
1423
+ Expr::PostfixInc { name, .. } => {
1424
+ let idx = self.name_idx(name);
1425
+ let one = self.constant_idx(Constant::Number(1.0));
1426
+ self.emit_u16(Opcode::LoadVar, idx);
1427
+ self.emit(Opcode::Dup);
1428
+ self.emit(Opcode::LoadConst);
1429
+ self.chunk.write_u16(one);
1430
+ self.emit_u8(Opcode::BinOp, 0);
1431
+ self.emit_u16(Opcode::StoreVar, idx);
1432
+ }
1433
+ Expr::PostfixDec { name, .. } => {
1434
+ let idx = self.name_idx(name);
1435
+ let one = self.constant_idx(Constant::Number(1.0));
1436
+ self.emit_u16(Opcode::LoadVar, idx);
1437
+ self.emit(Opcode::Dup);
1438
+ self.emit(Opcode::LoadConst);
1439
+ self.chunk.write_u16(one);
1440
+ self.emit_u8(Opcode::BinOp, 1);
1441
+ self.emit_u16(Opcode::StoreVar, idx);
1442
+ }
1443
+ Expr::PrefixInc { name, .. } => {
1444
+ let idx = self.name_idx(name);
1445
+ let one = self.constant_idx(Constant::Number(1.0));
1446
+ self.emit_u16(Opcode::LoadVar, idx);
1447
+ self.emit(Opcode::LoadConst);
1448
+ self.chunk.write_u16(one);
1449
+ self.emit_u8(Opcode::BinOp, 0);
1450
+ self.emit(Opcode::Dup);
1451
+ self.emit_u16(Opcode::StoreVar, idx);
1452
+ }
1453
+ Expr::PrefixDec { name, .. } => {
1454
+ let idx = self.name_idx(name);
1455
+ let one = self.constant_idx(Constant::Number(1.0));
1456
+ self.emit_u16(Opcode::LoadVar, idx);
1457
+ self.emit(Opcode::LoadConst);
1458
+ self.chunk.write_u16(one);
1459
+ self.emit_u8(Opcode::BinOp, 1);
1460
+ self.emit(Opcode::Dup);
1461
+ self.emit_u16(Opcode::StoreVar, idx);
1462
+ }
1463
+ Expr::CompoundAssign {
1464
+ name, op, value, ..
1465
+ } => {
1466
+ let idx = self.name_idx(name);
1467
+ self.emit_u16(Opcode::LoadVar, idx);
1468
+ self.compile_expr(value)?;
1469
+ self.emit_u8(Opcode::BinOp, compound_op_to_u8(*op));
1470
+ self.emit(Opcode::Dup);
1471
+ self.emit_u16(Opcode::StoreVar, idx);
1472
+ }
1473
+ Expr::MemberAssign {
1474
+ object,
1475
+ prop,
1476
+ value,
1477
+ ..
1478
+ } => {
1479
+ self.compile_expr(object)?;
1480
+ self.compile_expr(value)?;
1481
+ let idx = self.name_idx(prop);
1482
+ self.emit_u16(Opcode::SetMember, idx); // SetMember pops obj, val and pushes val back
1483
+ }
1484
+ Expr::IndexAssign {
1485
+ object,
1486
+ index,
1487
+ value,
1488
+ ..
1489
+ } => {
1490
+ self.compile_expr(object)?;
1491
+ self.compile_expr(index)?;
1492
+ self.compile_expr(value)?;
1493
+ self.emit(Opcode::Dup); // leave copy for assignment expression result
1494
+ self.emit(Opcode::SetIndex);
1495
+ }
1496
+ Expr::NativeModuleLoad {
1497
+ spec, export_name, ..
1498
+ } => {
1499
+ let spec_idx = self.constant_idx(Constant::String(Arc::clone(spec)));
1500
+ let export_idx = self.constant_idx(Constant::String(Arc::clone(export_name)));
1501
+ self.emit(Opcode::LoadNativeExport);
1502
+ self.chunk.write_u16(spec_idx);
1503
+ self.chunk.write_u16(export_idx);
1504
+ }
1505
+ Expr::JsxElement {
1506
+ tag,
1507
+ props,
1508
+ children,
1509
+ ..
1510
+ } => {
1511
+ self.compile_jsx_element(tag, props, children)?;
1512
+ }
1513
+ Expr::JsxFragment { children, .. } => {
1514
+ self.compile_jsx_fragment(children)?;
1515
+ }
1516
+ Expr::Await { operand, .. } => {
1517
+ // await expr => evaluate operand, then VM Opcode::AwaitPromise (throw on reject).
1518
+ self.compile_expr(operand)?;
1519
+ self.emit(Opcode::AwaitPromise);
1520
+ }
1521
+ Expr::LogicalAssign {
1522
+ name, op, value, ..
1523
+ } => {
1524
+ let idx = self.name_idx(name);
1525
+ match op {
1526
+ LogicalAssignOp::OrOr => {
1527
+ // ||= : if current is truthy, keep it; else eval rhs, assign, yield rhs
1528
+ self.emit_u16(Opcode::LoadVar, idx);
1529
+ self.emit(Opcode::Dup);
1530
+ let j_rhs = self.emit_jump(Opcode::JumpIfFalse);
1531
+ let j_end = self.emit_jump(Opcode::Jump);
1532
+ self.patch_jump(j_rhs, self.chunk.code.len());
1533
+ self.emit(Opcode::Pop);
1534
+ self.compile_expr(value)?;
1535
+ self.emit_u16(Opcode::StoreVar, idx);
1536
+ self.emit_u16(Opcode::LoadVar, idx);
1537
+ let end = self.chunk.code.len();
1538
+ self.patch_jump(j_end, end);
1539
+ }
1540
+ LogicalAssignOp::AndAnd => {
1541
+ // &&= : if current is falsy, keep it; else eval rhs, assign, yield rhs
1542
+ self.emit_u16(Opcode::LoadVar, idx);
1543
+ self.emit(Opcode::Dup);
1544
+ let j_short = self.emit_jump(Opcode::JumpIfFalse);
1545
+ self.emit(Opcode::Pop);
1546
+ self.compile_expr(value)?;
1547
+ self.emit_u16(Opcode::StoreVar, idx);
1548
+ self.emit_u16(Opcode::LoadVar, idx);
1549
+ let j_end = self.emit_jump(Opcode::Jump);
1550
+ let end = self.chunk.code.len();
1551
+ self.patch_jump(j_short, end);
1552
+ self.patch_jump(j_end, end);
1553
+ }
1554
+ LogicalAssignOp::Nullish => {
1555
+ // ??= : assign only when current === null (matches interpreter)
1556
+ let null_c = self.constant_idx(Constant::Null);
1557
+ self.emit_u16(Opcode::LoadVar, idx);
1558
+ self.emit(Opcode::Dup);
1559
+ self.emit(Opcode::LoadConst);
1560
+ self.chunk.write_u16(null_c);
1561
+ self.emit_u8(Opcode::BinOp, binop_to_u8(BinOp::StrictEq));
1562
+ let j_not_null = self.emit_jump(Opcode::JumpIfFalse);
1563
+ self.emit(Opcode::Pop);
1564
+ self.compile_expr(value)?;
1565
+ self.emit_u16(Opcode::StoreVar, idx);
1566
+ self.emit_u16(Opcode::LoadVar, idx);
1567
+ let j_end = self.emit_jump(Opcode::Jump);
1568
+ let end = self.chunk.code.len();
1569
+ self.patch_jump(j_not_null, end);
1570
+ self.patch_jump(j_end, end);
1571
+ }
1572
+ }
1573
+ }
1574
+ Expr::New { callee, args, .. } => {
1575
+ let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
1576
+ if has_spread {
1577
+ self.emit_u16(Opcode::NewArray, 0);
1578
+ for arg in args {
1579
+ match arg {
1580
+ CallArg::Expr(e) => {
1581
+ self.compile_expr(e)?;
1582
+ self.emit_u16(Opcode::NewArray, 1);
1583
+ self.emit(Opcode::ConcatArray);
1584
+ }
1585
+ CallArg::Spread(expr) => {
1586
+ self.compile_expr(expr)?;
1587
+ self.emit(Opcode::ConcatArray);
1588
+ }
1589
+ }
1590
+ }
1591
+ self.compile_expr(callee)?;
1592
+ self.emit(Opcode::ConstructSpread);
1593
+ } else {
1594
+ self.compile_expr(callee)?;
1595
+ for arg in args {
1596
+ if let CallArg::Expr(e) = arg {
1597
+ self.compile_expr(e)?;
1598
+ }
1599
+ }
1600
+ self.emit_u16(Opcode::Construct, args.len() as u16);
1601
+ }
1602
+ }
1603
+ }
1604
+ Ok(())
1605
+ }
1606
+
1607
+ fn compile_jsx_element(
1608
+ &mut self,
1609
+ tag: &Arc<str>,
1610
+ props: &[JsxProp],
1611
+ children: &[JsxChild],
1612
+ ) -> Result<(), CompileError> {
1613
+ let h_idx = self.name_idx(&Arc::from("h"));
1614
+ self.emit_u16(Opcode::LoadGlobal, h_idx);
1615
+ let tag_str = tag.as_ref();
1616
+ let is_component = tag_str
1617
+ .chars()
1618
+ .next()
1619
+ .map(|c| c.is_uppercase())
1620
+ .unwrap_or(false);
1621
+ if is_component {
1622
+ let tag_idx = self.name_idx(tag);
1623
+ self.emit_u16(Opcode::LoadGlobal, tag_idx);
1624
+ } else {
1625
+ let tag_const = self.constant_idx(Constant::String(Arc::from(tag_str)));
1626
+ self.emit(Opcode::LoadConst);
1627
+ self.chunk.write_u16(tag_const);
1628
+ }
1629
+ self.compile_jsx_props(props)?;
1630
+ self.compile_jsx_children(children)?;
1631
+ self.emit_u16(Opcode::Call, 3);
1632
+ Ok(())
1633
+ }
1634
+
1635
+ fn compile_jsx_fragment(&mut self, children: &[JsxChild]) -> Result<(), CompileError> {
1636
+ let h_idx = self.name_idx(&Arc::from("h"));
1637
+ self.emit_u16(Opcode::LoadGlobal, h_idx);
1638
+ let fragment_idx = self.name_idx(&Arc::from("Fragment"));
1639
+ self.emit_u16(Opcode::LoadGlobal, fragment_idx);
1640
+ let null_idx = self.constant_idx(Constant::Null);
1641
+ self.emit(Opcode::LoadConst);
1642
+ self.chunk.write_u16(null_idx);
1643
+ self.compile_jsx_children(children)?;
1644
+ self.emit_u16(Opcode::Call, 3);
1645
+ Ok(())
1646
+ }
1647
+
1648
+ fn compile_jsx_props(&mut self, props: &[JsxProp]) -> Result<(), CompileError> {
1649
+ if props.is_empty() {
1650
+ let null_idx = self.constant_idx(Constant::Null);
1651
+ self.emit(Opcode::LoadConst);
1652
+ self.chunk.write_u16(null_idx);
1653
+ return Ok(());
1654
+ }
1655
+ let has_spread = props.iter().any(|p| matches!(p, JsxProp::Spread(_)));
1656
+ if has_spread {
1657
+ self.emit_u16(Opcode::NewObject, 0);
1658
+ for prop in props {
1659
+ match prop {
1660
+ JsxProp::Attr { name, value } => {
1661
+ let key_idx = self.constant_idx(Constant::String(Arc::clone(name)));
1662
+ self.emit(Opcode::LoadConst);
1663
+ self.chunk.write_u16(key_idx);
1664
+ match value {
1665
+ JsxAttrValue::String(s) => {
1666
+ let val_idx = self.constant_idx(Constant::String(Arc::clone(s)));
1667
+ self.emit(Opcode::LoadConst);
1668
+ self.chunk.write_u16(val_idx);
1669
+ }
1670
+ JsxAttrValue::Expr(e) => self.compile_expr(e)?,
1671
+ JsxAttrValue::ImplicitTrue => {
1672
+ let true_idx = self.constant_idx(Constant::Bool(true));
1673
+ self.emit(Opcode::LoadConst);
1674
+ self.chunk.write_u16(true_idx);
1675
+ }
1676
+ }
1677
+ self.emit_u16(Opcode::NewObject, 1);
1678
+ self.emit(Opcode::MergeObject);
1679
+ }
1680
+ JsxProp::Spread(expr) => {
1681
+ self.compile_expr(expr)?;
1682
+ self.emit(Opcode::MergeObject);
1683
+ }
1684
+ }
1685
+ }
1686
+ } else {
1687
+ for prop in props {
1688
+ if let JsxProp::Attr { name, value } = prop {
1689
+ let key_idx = self.constant_idx(Constant::String(Arc::clone(name)));
1690
+ self.emit(Opcode::LoadConst);
1691
+ self.chunk.write_u16(key_idx);
1692
+ match value {
1693
+ JsxAttrValue::String(s) => {
1694
+ let val_idx = self.constant_idx(Constant::String(Arc::clone(s)));
1695
+ self.emit(Opcode::LoadConst);
1696
+ self.chunk.write_u16(val_idx);
1697
+ }
1698
+ JsxAttrValue::Expr(e) => self.compile_expr(e)?,
1699
+ JsxAttrValue::ImplicitTrue => {
1700
+ let true_idx = self.constant_idx(Constant::Bool(true));
1701
+ self.emit(Opcode::LoadConst);
1702
+ self.chunk.write_u16(true_idx);
1703
+ }
1704
+ }
1705
+ }
1706
+ }
1707
+ self.emit_u16(Opcode::NewObject, props.len() as u16);
1708
+ }
1709
+ Ok(())
1710
+ }
1711
+
1712
+ fn compile_jsx_children(&mut self, children: &[JsxChild]) -> Result<(), CompileError> {
1713
+ for child in children {
1714
+ match child {
1715
+ JsxChild::Text(s) => {
1716
+ let idx = self.constant_idx(Constant::String(Arc::clone(s)));
1717
+ self.emit(Opcode::LoadConst);
1718
+ self.chunk.write_u16(idx);
1719
+ }
1720
+ JsxChild::Expr(e) => self.compile_expr(e)?,
1721
+ }
1722
+ }
1723
+ self.emit_u16(Opcode::NewArray, children.len() as u16);
1724
+ Ok(())
1725
+ }
1726
+ }
1727
+
1728
+ /// Compile a Tish program to bytecode (with peephole optimizations).
1729
+ pub fn compile(program: &Program) -> Result<Chunk, CompileError> {
1730
+ compile_internal(program, true, false)
1731
+ }
1732
+
1733
+ /// Compile without peephole optimizations (for --no-optimize).
1734
+ pub fn compile_unoptimized(program: &Program) -> Result<Chunk, CompileError> {
1735
+ compile_internal(program, false, false)
1736
+ }
1737
+
1738
+ /// Compile for REPL: last expression statement leaves its value on the stack (no Pop, no trailing Null).
1739
+ pub fn compile_for_repl(program: &Program) -> Result<Chunk, CompileError> {
1740
+ compile_internal(program, true, true)
1741
+ }
1742
+
1743
+ /// Compile for REPL without peephole optimizations.
1744
+ pub fn compile_for_repl_unoptimized(program: &Program) -> Result<Chunk, CompileError> {
1745
+ compile_internal(program, false, true)
1746
+ }
1747
+
1748
+ fn compile_internal(
1749
+ program: &Program,
1750
+ peephole: bool,
1751
+ retain_last_expr: bool,
1752
+ ) -> Result<Chunk, CompileError> {
1753
+ let mut chunk = Chunk::new();
1754
+ let mut compiler = Compiler::new(&mut chunk, retain_last_expr);
1755
+ compiler.compile_program(program)?;
1756
+ if peephole {
1757
+ crate::peephole::optimize(&mut chunk);
1758
+ }
1759
+ Ok(chunk)
1760
+ }