1use std::collections::{HashMap, HashSet};
4use std::sync::Arc;
5
6use tishlang_ast::{
7 ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern,
8 ExportDeclaration, Expr, FunParam, JsxAttrValue, JsxChild, JsxProp, Literal, LogicalAssignOp,
9 MemberProp, ObjectProp, Program, Span, Statement,
10};
11
12use crate::chunk::{Chunk, Constant};
13use crate::encoding::{binop_to_u8, compound_op_to_u8, unaryop_to_u8};
14use crate::opcode::{MathUnaryFn, Opcode};
15
16enum SimpleMapResult {
17 Identity,
18 BinOp(BinOp, Constant, bool), }
20
21fn is_string_typed(expr: &Expr) -> bool {
26 match expr {
27 Expr::Literal {
28 value: Literal::String(_),
29 ..
30 } => true,
31 Expr::TemplateLiteral { .. } => true,
32 Expr::Binary {
33 left,
34 op: BinOp::Add,
35 right,
36 ..
37 } => is_string_typed(left) || is_string_typed(right),
38 _ => false,
39 }
40}
41
42fn literal_to_constant(expr: &Expr) -> Option<Constant> {
43 if let Expr::Literal { value, .. } = expr {
44 Some(match value {
45 Literal::Number(n) => Constant::Number(*n),
46 Literal::String(s) => Constant::String(Arc::clone(s)),
47 Literal::Bool(b) => Constant::Bool(*b),
48 Literal::Null => Constant::Null,
49 })
50 } else {
51 None
52 }
53}
54
55#[derive(Debug)]
56pub struct CompileError {
57 pub message: String,
58}
59
60impl std::fmt::Display for CompileError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "{}", self.message)
63 }
64}
65
66impl std::error::Error for CompileError {}
67
68struct LoopInfo {
70 break_patches: Vec<usize>,
71 continue_patches: Vec<usize>,
74 continue_is_forward_jump: bool,
78}
79
80struct SwitchInfo {
82 break_patches: Vec<usize>,
83}
84
85#[derive(Clone, Copy)]
87enum Breakable {
88 Loop { unwind_depth: usize },
90 Switch { unwind_depth: usize },
92}
93
94struct Compiler<'a> {
95 chunk: &'a mut Chunk,
96 scope: Vec<HashMap<Arc<str>, bool>>,
98 loop_stack: Vec<LoopInfo>,
100 switch_stack: Vec<SwitchInfo>,
101 breakable_stack: Vec<Breakable>,
103 block_depth: usize,
105 retain_last_expr: bool,
107 slot_ctx: Option<HashMap<Arc<str>, u16>>,
111 slot_scopes: Vec<HashMap<Arc<str>, u16>>,
117 slot_captured: HashSet<Arc<str>>,
119 next_slot: u16,
121 general_slots: bool,
123 finally_stack: Vec<Statement>,
128 self_fn_name: Option<Arc<str>>,
134 math_is_global: bool,
139 stable_globals: Arc<std::collections::HashSet<Arc<str>>>,
144}
145
146fn expr_is_param_only(e: &Expr, params: &HashSet<&str>) -> bool {
149 match e {
150 Expr::Literal { .. } => true,
151 Expr::Ident { name, .. } => params.contains(name.as_ref()),
152 Expr::Binary { left, right, .. } => {
153 expr_is_param_only(left, params) && expr_is_param_only(right, params)
154 }
155 Expr::Unary { operand, .. } => expr_is_param_only(operand, params),
156 Expr::Call { callee, args, .. } => {
157 expr_is_param_only(callee, params)
158 && args.iter().all(|a| match a {
159 CallArg::Expr(x) => expr_is_param_only(x, params),
160 CallArg::Spread(_) => false,
161 })
162 }
163 Expr::Member { object, prop, .. } => {
164 expr_is_param_only(object, params)
165 && match prop {
166 MemberProp::Name { .. } => true,
167 MemberProp::Expr(x) => expr_is_param_only(x, params),
168 }
169 }
170 Expr::Index { object, index, .. } => {
171 expr_is_param_only(object, params) && expr_is_param_only(index, params)
172 }
173 Expr::Conditional {
174 cond,
175 then_branch,
176 else_branch,
177 ..
178 } => {
179 expr_is_param_only(cond, params)
180 && expr_is_param_only(then_branch, params)
181 && expr_is_param_only(else_branch, params)
182 }
183 Expr::NullishCoalesce { left, right, .. } => {
184 expr_is_param_only(left, params) && expr_is_param_only(right, params)
185 }
186 Expr::Array { elements, .. } => elements.iter().all(|el| match el {
187 ArrayElement::Expr(x) => expr_is_param_only(x, params),
188 ArrayElement::Spread(_) => false,
189 }),
190 Expr::Object { props, .. } => props.iter().all(|p| match p {
191 ObjectProp::KeyValue(_, x, _) => expr_is_param_only(x, params),
192 ObjectProp::Spread(_) => false,
193 }),
194 Expr::TemplateLiteral { exprs, .. } => exprs.iter().all(|x| expr_is_param_only(x, params)),
195 Expr::TypeOf { operand, .. } => expr_is_param_only(operand, params),
196 _ => false,
198 }
199}
200
201fn stmt_is_param_only(s: &Statement, params: &HashSet<&str>) -> bool {
205 match s {
206 Statement::Block { statements, .. } => {
207 statements.iter().all(|st| stmt_is_param_only(st, params))
208 }
209 Statement::Multi { statements, .. } => {
210 statements.iter().all(|st| stmt_is_param_only(st, params))
211 }
212 Statement::ExprStmt { expr, .. } => expr_is_param_only(expr, params),
213 Statement::Return { value, .. } => {
214 value.as_ref().is_none_or(|e| expr_is_param_only(e, params))
215 }
216 Statement::If {
217 cond,
218 then_branch,
219 else_branch,
220 ..
221 } => {
222 expr_is_param_only(cond, params)
223 && stmt_is_param_only(then_branch, params)
224 && else_branch
225 .as_ref()
226 .is_none_or(|b| stmt_is_param_only(b, params))
227 }
228 _ => false,
229 }
230}
231
232fn simple_fn_slots(
238 params: &[FunParam],
239 has_rest: bool,
240 body_ok: impl FnOnce(&HashSet<&str>) -> bool,
241) -> Option<HashMap<Arc<str>, u16>> {
242 if has_rest {
243 return None;
244 }
245 let mut map: HashMap<Arc<str>, u16> = HashMap::with_capacity(params.len());
246 for (i, p) in params.iter().enumerate() {
247 match p {
248 FunParam::Simple(tp) => {
249 map.insert(Arc::clone(&tp.name), i as u16);
250 }
251 FunParam::Destructure { .. } => return None,
252 }
253 }
254 let pset: HashSet<&str> = map.keys().map(|k| k.as_ref()).collect();
255 if body_ok(&pset) {
256 Some(map)
257 } else {
258 None
259 }
260}
261
262fn slots_enabled() -> bool {
266 std::env::var("TISH_VM_SLOTS")
267 .map(|v| v != "0")
268 .unwrap_or(true)
269}
270
271fn params_bind_name(params: &[FunParam], name: &str) -> bool {
274 params.iter().any(|p| match p {
275 FunParam::Simple(tp) => tp.name.as_ref() == name,
276 FunParam::Destructure { .. } => true,
277 })
278}
279
280fn params_default_rebinds(params: &[FunParam], name: &str) -> bool {
284 params.iter().any(|p| {
285 let default = match p {
286 FunParam::Simple(tp) => &tp.default,
287 FunParam::Destructure { default, .. } => default,
288 };
289 default.as_ref().is_some_and(|e| expr_rebinds(e, name))
290 })
291}
292
293fn stmt_rebinds(s: &Statement, name: &str) -> bool {
300 match s {
301 Statement::Block { statements, .. } => statements.iter().any(|s| stmt_rebinds(s, name)),
302 Statement::Multi { statements, .. } => statements.iter().any(|s| stmt_rebinds(s, name)),
303 Statement::VarDecl { name: n, init, .. } => {
304 n.as_ref() == name || init.as_ref().is_some_and(|e| expr_rebinds(e, name))
305 }
306 Statement::ExprStmt { expr, .. } => expr_rebinds(expr, name),
307 Statement::Return { value, .. } => value.as_ref().is_some_and(|e| expr_rebinds(e, name)),
308 Statement::Throw { value, .. } => expr_rebinds(value, name),
309 Statement::If {
310 cond,
311 then_branch,
312 else_branch,
313 ..
314 } => {
315 expr_rebinds(cond, name)
316 || stmt_rebinds(then_branch, name)
317 || else_branch.as_ref().is_some_and(|s| stmt_rebinds(s, name))
318 }
319 Statement::While { cond, body, .. } => expr_rebinds(cond, name) || stmt_rebinds(body, name),
320 Statement::DoWhile { body, cond, .. } => {
321 stmt_rebinds(body, name) || expr_rebinds(cond, name)
322 }
323 Statement::For {
324 init,
325 cond,
326 update,
327 body,
328 ..
329 } => {
330 init.as_ref().is_some_and(|s| stmt_rebinds(s, name))
331 || cond.as_ref().is_some_and(|e| expr_rebinds(e, name))
332 || update.as_ref().is_some_and(|e| expr_rebinds(e, name))
333 || stmt_rebinds(body, name)
334 }
335 Statement::ForOf {
336 name: n,
337 iterable: e,
338 body,
339 ..
340 }
341 | Statement::ForIn {
342 name: n,
343 object: e,
344 body,
345 ..
346 } => n.as_ref() == name || expr_rebinds(e, name) || stmt_rebinds(body, name),
347 Statement::Switch {
348 expr,
349 cases,
350 default_body,
351 ..
352 } => {
353 expr_rebinds(expr, name)
354 || cases.iter().any(|(t, body)| {
355 t.as_ref().is_some_and(|e| expr_rebinds(e, name))
356 || body.iter().any(|s| stmt_rebinds(s, name))
357 })
358 || default_body
359 .as_ref()
360 .is_some_and(|b| b.iter().any(|s| stmt_rebinds(s, name)))
361 }
362 Statement::Try {
363 body,
364 catch_body,
365 finally_body,
366 ..
367 } => {
368 stmt_rebinds(body, name)
369 || catch_body.as_ref().is_some_and(|s| stmt_rebinds(s, name))
370 || finally_body.as_ref().is_some_and(|s| stmt_rebinds(s, name))
371 }
372 Statement::Break { .. } | Statement::Continue { .. } => false,
373 _ => true,
376 }
377}
378
379fn expr_rebinds(e: &Expr, name: &str) -> bool {
381 match e {
382 Expr::Assign { name: n, value, .. }
383 | Expr::CompoundAssign { name: n, value, .. }
384 | Expr::LogicalAssign { name: n, value, .. } => {
385 n.as_ref() == name || expr_rebinds(value, name)
386 }
387 Expr::PostfixInc { name: n, .. }
388 | Expr::PostfixDec { name: n, .. }
389 | Expr::PrefixInc { name: n, .. }
390 | Expr::PrefixDec { name: n, .. } => n.as_ref() == name,
391 Expr::Literal { .. } | Expr::Ident { .. } => false,
392 Expr::Binary { left, right, .. } | Expr::NullishCoalesce { left, right, .. } => {
393 expr_rebinds(left, name) || expr_rebinds(right, name)
394 }
395 Expr::Unary { operand, .. }
396 | Expr::TypeOf { operand, .. }
397 | Expr::Await { operand, .. } => expr_rebinds(operand, name),
398 Expr::Conditional {
399 cond,
400 then_branch,
401 else_branch,
402 ..
403 } => {
404 expr_rebinds(cond, name)
405 || expr_rebinds(then_branch, name)
406 || expr_rebinds(else_branch, name)
407 }
408 Expr::Call { callee, args, .. } | Expr::New { callee, args, .. } => {
409 expr_rebinds(callee, name)
410 || args.iter().any(|a| match a {
411 CallArg::Expr(e) | CallArg::Spread(e) => expr_rebinds(e, name),
412 })
413 }
414 Expr::Member { object, .. } => expr_rebinds(object, name),
415 Expr::Index { object, index, .. } => {
416 expr_rebinds(object, name) || expr_rebinds(index, name)
417 }
418 Expr::Array { elements, .. } => elements.iter().any(|el| match el {
419 ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_rebinds(e, name),
420 }),
421 Expr::Object { props, .. } => props.iter().any(|p| match p {
422 ObjectProp::KeyValue(_, e, _) | ObjectProp::Spread(e) => expr_rebinds(e, name),
423 }),
424 Expr::MemberAssign { object, value, .. } => {
425 expr_rebinds(object, name) || expr_rebinds(value, name)
426 }
427 Expr::IndexAssign {
428 object,
429 index,
430 value,
431 ..
432 } => expr_rebinds(object, name) || expr_rebinds(index, name) || expr_rebinds(value, name),
433 Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(|e| expr_rebinds(e, name)),
434 Expr::ArrowFunction { params, body, .. } => {
438 params_default_rebinds(params, name)
439 || match body {
440 ArrowBody::Expr(e) => expr_rebinds(e, name),
441 ArrowBody::Block(s) => stmt_rebinds(s, name),
442 }
443 }
444 _ => true,
446 }
447}
448
449fn name_rebinds_in_stmt(s: &Statement, name: &str) -> bool {
456 match s {
457 Statement::FunDecl {
458 name: n,
459 params,
460 rest_param,
461 body,
462 ..
463 } => {
464 n.as_ref() == name
465 || params_bind_name(params, name)
466 || params_default_rebinds(params, name)
467 || rest_param
468 .as_ref()
469 .is_some_and(|rp| rp.name.as_ref() == name)
470 || name_rebinds_in_stmt(body, name)
471 }
472 Statement::Block { statements, .. } | Statement::Multi { statements, .. } => {
473 statements.iter().any(|s| name_rebinds_in_stmt(s, name))
474 }
475 Statement::VarDecl { name: n, init, .. } => {
476 n.as_ref() == name || init.as_ref().is_some_and(|e| expr_rebinds(e, name))
477 }
478 Statement::ExprStmt { expr, .. } => expr_rebinds(expr, name),
479 Statement::Return { value, .. } => value.as_ref().is_some_and(|e| expr_rebinds(e, name)),
480 Statement::Throw { value, .. } => expr_rebinds(value, name),
481 Statement::If {
482 cond,
483 then_branch,
484 else_branch,
485 ..
486 } => {
487 expr_rebinds(cond, name)
488 || name_rebinds_in_stmt(then_branch, name)
489 || else_branch
490 .as_ref()
491 .is_some_and(|s| name_rebinds_in_stmt(s, name))
492 }
493 Statement::While { cond, body, .. } => {
494 expr_rebinds(cond, name) || name_rebinds_in_stmt(body, name)
495 }
496 Statement::DoWhile { body, cond, .. } => {
497 name_rebinds_in_stmt(body, name) || expr_rebinds(cond, name)
498 }
499 Statement::For {
500 init,
501 cond,
502 update,
503 body,
504 ..
505 } => {
506 init.as_ref().is_some_and(|s| name_rebinds_in_stmt(s, name))
507 || cond.as_ref().is_some_and(|e| expr_rebinds(e, name))
508 || update.as_ref().is_some_and(|e| expr_rebinds(e, name))
509 || name_rebinds_in_stmt(body, name)
510 }
511 Statement::ForOf {
512 name: n,
513 iterable: e,
514 body,
515 ..
516 }
517 | Statement::ForIn {
518 name: n,
519 object: e,
520 body,
521 ..
522 } => n.as_ref() == name || expr_rebinds(e, name) || name_rebinds_in_stmt(body, name),
523 Statement::Switch {
524 expr,
525 cases,
526 default_body,
527 ..
528 } => {
529 expr_rebinds(expr, name)
530 || cases.iter().any(|(t, body)| {
531 t.as_ref().is_some_and(|e| expr_rebinds(e, name))
532 || body.iter().any(|s| name_rebinds_in_stmt(s, name))
533 })
534 || default_body
535 .as_ref()
536 .is_some_and(|b| b.iter().any(|s| name_rebinds_in_stmt(s, name)))
537 }
538 Statement::Try {
539 body,
540 catch_body,
541 finally_body,
542 ..
543 } => {
544 name_rebinds_in_stmt(body, name)
545 || catch_body
546 .as_ref()
547 .is_some_and(|s| name_rebinds_in_stmt(s, name))
548 || finally_body
549 .as_ref()
550 .is_some_and(|s| name_rebinds_in_stmt(s, name))
551 }
552 Statement::Break { .. } | Statement::Continue { .. } => false,
553 _ => true,
555 }
556}
557
558fn compute_stable_globals(program: &Program) -> std::collections::HashSet<Arc<str>> {
564 let mut fn_count: HashMap<Arc<str>, usize> = HashMap::new();
565 let mut nonfn_toplevel: std::collections::HashSet<Arc<str>> = std::collections::HashSet::new();
566 for s in &program.statements {
567 match s {
568 Statement::FunDecl { name, .. } => *fn_count.entry(Arc::clone(name)).or_insert(0) += 1,
569 Statement::VarDecl { name, .. } => {
570 nonfn_toplevel.insert(Arc::clone(name));
571 }
572 _ => {}
573 }
574 }
575 let mut stable: std::collections::HashSet<Arc<str>> = std::collections::HashSet::new();
576 'cand: for (name, &count) in &fn_count {
577 if count != 1 || nonfn_toplevel.contains(name) {
578 continue;
579 }
580 for s in &program.statements {
581 match s {
582 Statement::FunDecl {
584 name: n,
585 params,
586 rest_param,
587 body,
588 ..
589 } if n == name => {
590 if params_bind_name(params, name)
591 || params_default_rebinds(params, name)
592 || rest_param
593 .as_ref()
594 .is_some_and(|rp| rp.name.as_ref() == name.as_ref())
595 || name_rebinds_in_stmt(body, name)
596 {
597 continue 'cand;
598 }
599 }
600 other => {
601 if name_rebinds_in_stmt(other, name) {
602 continue 'cand;
603 }
604 }
605 }
606 }
607 stable.insert(Arc::clone(name));
608 }
609 stable
610}
611
612#[derive(Default)]
622struct SlotScan {
623 captured: HashSet<Arc<str>>,
624}
625
626impl SlotScan {
627 fn stmt(&mut self, s: &Statement, in_closure: bool) -> bool {
628 match s {
629 Statement::Block { statements, .. } => {
630 statements.iter().all(|s| self.stmt(s, in_closure))
631 }
632 Statement::Multi { statements, .. } => {
633 statements.iter().all(|s| self.stmt(s, in_closure))
634 }
635 Statement::VarDecl { init, .. } => {
636 init.as_ref().is_none_or(|e| self.expr(e, in_closure))
637 }
638 Statement::VarDeclDestructure { init, .. } => self.expr(init, in_closure),
639 Statement::ExprStmt { expr, .. } => self.expr(expr, in_closure),
640 Statement::If {
641 cond,
642 then_branch,
643 else_branch,
644 ..
645 } => {
646 self.expr(cond, in_closure)
647 && self.stmt(then_branch, in_closure)
648 && else_branch
649 .as_ref()
650 .is_none_or(|s| self.stmt(s, in_closure))
651 }
652 Statement::While { cond, body, .. } => {
653 self.expr(cond, in_closure) && self.stmt(body, in_closure)
654 }
655 Statement::DoWhile { body, cond, .. } => {
656 self.stmt(body, in_closure) && self.expr(cond, in_closure)
657 }
658 Statement::For {
659 init,
660 cond,
661 update,
662 body,
663 ..
664 } => {
665 init.as_ref().is_none_or(|i| self.stmt(i, in_closure))
666 && cond.as_ref().is_none_or(|e| self.expr(e, in_closure))
667 && update.as_ref().is_none_or(|e| self.expr(e, in_closure))
668 && self.stmt(body, in_closure)
669 }
670 Statement::ForOf { iterable, body, .. } => {
671 self.expr(iterable, in_closure) && self.stmt(body, in_closure)
672 }
673 Statement::Return { value, .. } => {
674 value.as_ref().is_none_or(|e| self.expr(e, in_closure))
675 }
676 Statement::Throw { value, .. } => self.expr(value, in_closure),
677 Statement::Break { .. } | Statement::Continue { .. } => true,
678 Statement::Switch {
679 expr,
680 cases,
681 default_body,
682 ..
683 } => {
684 if !self.expr(expr, in_closure) {
685 return false;
686 }
687 for (test, body) in cases {
688 if let Some(t) = test {
689 if !self.expr(t, in_closure) {
690 return false;
691 }
692 }
693 if !body.iter().all(|s| self.stmt(s, in_closure)) {
694 return false;
695 }
696 }
697 default_body
698 .as_ref()
699 .is_none_or(|b| b.iter().all(|s| self.stmt(s, in_closure)))
700 }
701 Statement::Try {
702 body,
703 catch_body,
704 finally_body,
705 ..
706 } => {
707 self.stmt(body, in_closure)
708 && catch_body.as_ref().is_none_or(|s| self.stmt(s, in_closure))
709 && finally_body
710 .as_ref()
711 .is_none_or(|s| self.stmt(s, in_closure))
712 }
713 Statement::FunDecl { params, body, .. } => {
715 self.scan_closure_param_defaults(params) && self.stmt(body, true)
716 }
717 _ => false,
719 }
720 }
721
722 fn expr(&mut self, e: &Expr, in_closure: bool) -> bool {
723 match e {
724 Expr::Literal { .. } => true,
725 Expr::Ident { name, .. } => {
726 if in_closure {
727 self.captured.insert(Arc::clone(name));
728 }
729 true
730 }
731 Expr::Binary { left, right, .. } => {
732 self.expr(left, in_closure) && self.expr(right, in_closure)
733 }
734 Expr::Unary { operand, .. }
735 | Expr::TypeOf { operand, .. }
736 | Expr::Await { operand, .. } => self.expr(operand, in_closure),
737 Expr::Conditional {
738 cond,
739 then_branch,
740 else_branch,
741 ..
742 } => {
743 self.expr(cond, in_closure)
744 && self.expr(then_branch, in_closure)
745 && self.expr(else_branch, in_closure)
746 }
747 Expr::NullishCoalesce { left, right, .. } => {
748 self.expr(left, in_closure) && self.expr(right, in_closure)
749 }
750 Expr::Call { callee, args, .. } | Expr::New { callee, args, .. } => {
751 self.expr(callee, in_closure)
752 && args.iter().all(|a| match a {
753 CallArg::Expr(e) | CallArg::Spread(e) => self.expr(e, in_closure),
754 })
755 }
756 Expr::Member { object, .. } => self.expr(object, in_closure),
757 Expr::Index { object, index, .. } => {
758 self.expr(object, in_closure) && self.expr(index, in_closure)
759 }
760 Expr::Array { elements, .. } => elements.iter().all(|el| match el {
761 ArrayElement::Expr(e) | ArrayElement::Spread(e) => self.expr(e, in_closure),
762 }),
763 Expr::Object { props, .. } => props.iter().all(|p| match p {
764 ObjectProp::KeyValue(_, e, _) | ObjectProp::Spread(e) => self.expr(e, in_closure),
765 }),
766 Expr::Assign { name, value, .. }
767 | Expr::CompoundAssign { name, value, .. }
768 | Expr::LogicalAssign { name, value, .. } => {
769 if in_closure {
770 self.captured.insert(Arc::clone(name));
771 }
772 self.expr(value, in_closure)
773 }
774 Expr::MemberAssign { object, value, .. } => {
775 self.expr(object, in_closure) && self.expr(value, in_closure)
776 }
777 Expr::IndexAssign {
778 object,
779 index,
780 value,
781 ..
782 } => {
783 self.expr(object, in_closure)
784 && self.expr(index, in_closure)
785 && self.expr(value, in_closure)
786 }
787 Expr::PostfixInc { name, .. }
788 | Expr::PostfixDec { name, .. }
789 | Expr::PrefixInc { name, .. }
790 | Expr::PrefixDec { name, .. } => {
791 if in_closure {
792 self.captured.insert(Arc::clone(name));
793 }
794 true
795 }
796 Expr::TemplateLiteral { exprs, .. } => exprs.iter().all(|e| self.expr(e, in_closure)),
797 Expr::ArrowFunction { params, body, .. } => {
798 if !self.scan_closure_param_defaults(params) {
799 return false;
800 }
801 match body {
802 ArrowBody::Expr(e) => self.expr(e, true),
803 ArrowBody::Block(s) => self.stmt(s, true),
804 }
805 }
806 _ => false,
808 }
809 }
810
811 fn scan_closure_param_defaults(&mut self, params: &[FunParam]) -> bool {
813 for p in params {
814 let default = match p {
815 FunParam::Simple(tp) => &tp.default,
816 FunParam::Destructure { default, .. } => default,
817 };
818 if let Some(d) = default {
819 if !self.expr(d, true) {
820 return false;
821 }
822 }
823 }
824 true
825 }
826}
827
828fn slot_analyze(
833 params: &[FunParam],
834 has_rest: bool,
835 body: &Statement,
836) -> Option<HashSet<Arc<str>>> {
837 if !slots_enabled() || has_rest {
838 return None;
839 }
840 for p in params {
841 if let FunParam::Destructure { .. } = p {
842 return None;
843 }
844 }
845 let mut scan = SlotScan::default();
846 if !scan.stmt(body, false) {
847 return None;
848 }
849 for p in params {
850 if let FunParam::Simple(tp) = p {
851 if scan.captured.contains(&tp.name) {
852 return None;
853 }
854 }
855 }
856 Some(scan.captured)
857}
858
859fn slot_analyze_toplevel(statements: &[Statement]) -> Option<HashSet<Arc<str>>> {
861 if !slots_enabled() {
862 return None;
863 }
864 let mut scan = SlotScan::default();
865 for s in statements {
866 if !scan.stmt(s, false) {
867 return None;
868 }
869 }
870 Some(scan.captured)
871}
872
873impl<'a> Compiler<'a> {
874 #[inline]
878 fn resolve_slot(&self, name: &str) -> Option<u16> {
879 if let Some(m) = self.slot_ctx.as_ref() {
880 if let Some(s) = m.get(name) {
881 return Some(*s);
882 }
883 }
884 self.slot_scopes
885 .iter()
886 .rev()
887 .find_map(|m| m.get(name).copied())
888 }
889
890 fn emit_var_load(&mut self, name: &Arc<str>) {
892 if let Some(slot) = self.resolve_slot(name) {
893 self.emit_u16(Opcode::LoadLocal, slot);
894 } else {
895 let idx = self.name_idx(name);
896 self.emit_u16(Opcode::LoadVar, idx);
897 }
898 }
899
900 fn emit_var_store(&mut self, name: &Arc<str>) {
902 if let Some(slot) = self.resolve_slot(name) {
903 self.emit_u16(Opcode::StoreLocal, slot);
904 } else {
905 let idx = self.name_idx(name);
906 self.emit_u16(Opcode::StoreVar, idx);
907 }
908 }
909
910 fn new(chunk: &'a mut Chunk, retain_last_expr: bool) -> Self {
911 Self {
912 chunk,
913 scope: vec![HashMap::new()],
914 loop_stack: Vec::new(),
915 switch_stack: Vec::new(),
916 breakable_stack: Vec::new(),
917 block_depth: 0,
918 retain_last_expr,
919 slot_ctx: None,
920 slot_scopes: Vec::new(),
921 slot_captured: HashSet::new(),
922 next_slot: 0,
923 general_slots: false,
924 finally_stack: Vec::new(),
925 self_fn_name: None,
926 math_is_global: false,
927 stable_globals: Arc::new(std::collections::HashSet::new()),
928 }
929 }
930
931 fn enter_block_scope(&mut self) {
934 self.scope.push(HashMap::default());
935 if self.general_slots {
936 self.slot_scopes.push(HashMap::default());
937 }
938 }
939
940 fn exit_block_scope(&mut self) {
941 let _popped = self.scope.pop();
942 if self.general_slots {
943 self.slot_scopes.pop();
944 }
945 }
946
947 fn declare_slot(&mut self, name: &Arc<str>) -> u16 {
949 let slot = self.next_slot;
950 self.next_slot += 1;
951 if let Some(frame) = self.slot_scopes.last_mut() {
952 frame.insert(Arc::clone(name), slot);
953 }
954 slot
955 }
956
957 fn emit_pending_finallys(&mut self) -> Result<(), CompileError> {
960 if self.finally_stack.is_empty() {
961 return Ok(());
962 }
963 let saved = std::mem::take(&mut self.finally_stack);
964 for finally in saved.iter().rev() {
965 self.compile_statement(finally)?;
966 }
967 self.finally_stack = saved;
968 Ok(())
969 }
970
971 fn emit_exit_blocks_until_depth(&mut self, target_depth: usize) {
972 let n = self.block_depth.saturating_sub(target_depth);
973 for _ in 0..n {
974 self.emit(Opcode::ExitBlock);
975 }
976 }
977
978 #[allow(clippy::type_complexity)] fn plan_function_params(
982 params: &[FunParam],
983 ) -> Result<(Vec<Arc<str>>, Vec<Option<DestructPattern>>), CompileError> {
984 let mut names = Vec::with_capacity(params.len());
985 let mut slots: Vec<Option<DestructPattern>> = Vec::with_capacity(params.len());
986 let mut syn_counter = 0u32;
987 for p in params {
988 match p {
989 FunParam::Simple(tp) => {
990 names.push(Arc::clone(&tp.name));
991 slots.push(None);
992 }
993 FunParam::Destructure {
994 pattern, default, ..
995 } => {
996 if default.is_some() {
997 return Err(CompileError {
998 message: "Default values on destructuring parameters are not supported in bytecode"
999 .to_string(),
1000 });
1001 }
1002 names.push(Arc::from(format!("__param_{}", syn_counter)));
1003 syn_counter += 1;
1004 slots.push(Some(pattern.clone()));
1005 }
1006 }
1007 }
1008 Ok((names, slots))
1009 }
1010
1011 fn emit_param_destructure_prologue(
1013 &mut self,
1014 param_names: &[Arc<str>],
1015 slots: &[Option<DestructPattern>],
1016 ) -> Result<(), CompileError> {
1017 debug_assert_eq!(param_names.len(), slots.len());
1018 for (name, slot) in param_names.iter().zip(slots.iter()) {
1019 if let Some(pattern) = slot {
1020 let idx = self.name_idx(name);
1021 self.emit_u16(Opcode::LoadVar, idx);
1022 self.compile_destructure(pattern, false, false)?;
1023 }
1024 }
1025 Ok(())
1026 }
1027
1028 fn emit_param_defaults_prologue(&mut self, params: &[FunParam]) -> Result<(), CompileError> {
1038 for (i, p) in params.iter().enumerate() {
1039 let FunParam::Simple(tp) = p else { continue };
1040 let Some(default_expr) = &tp.default else {
1041 continue;
1042 };
1043 self.emit_u16(Opcode::ArgMissing, i as u16);
1044 let skip = self.emit_jump(Opcode::JumpIfFalse);
1045 self.compile_expr(default_expr)?;
1046 let slot = self
1047 .slot_ctx
1048 .as_ref()
1049 .and_then(|m| m.get(tp.name.as_ref()))
1050 .copied();
1051 match slot {
1052 Some(slot) => self.emit_u16(Opcode::StoreLocal, slot),
1053 None => {
1054 let idx = self.name_idx(&tp.name);
1055 self.emit_u16(Opcode::DeclareVarPlain, idx);
1056 }
1057 }
1058 self.patch_jump(skip, self.chunk.code.len());
1059 }
1060 Ok(())
1061 }
1062
1063 fn loop_body_block_lets(body: &Statement) -> Vec<Arc<str>> {
1067 let mut out = Vec::new();
1068 if let Statement::Block { statements, .. } = body {
1069 for s in statements {
1070 if let Statement::VarDecl { name, .. } = s {
1071 out.push(Arc::clone(name));
1072 }
1073 }
1074 }
1075 out
1076 }
1077
1078 fn compile_for_init_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
1079 match stmt {
1080 Statement::VarDecl {
1081 name,
1082 init,
1083 mutable: _,
1084 ..
1085 } => {
1086 if let Some(expr) = init {
1087 self.compile_expr(expr)?;
1088 } else {
1089 let idx = self.constant_idx(Constant::Null);
1090 self.emit(Opcode::LoadConst);
1091 self.chunk.write_u16(idx);
1092 }
1093 if self.general_slots && !self.slot_captured.contains(name.as_ref()) {
1094 let slot = self.declare_slot(name);
1095 self.emit_u16(Opcode::StoreLocal, slot);
1096 } else {
1097 let idx = self.name_idx(name);
1098 self.emit_u16(Opcode::DeclareVarPlain, idx);
1099 self.scope
1100 .last_mut()
1101 .unwrap()
1102 .insert(Arc::clone(name), false);
1103 }
1104 }
1105 Statement::VarDeclDestructure { pattern, init, .. } => {
1106 self.compile_expr(init)?;
1107 self.compile_destructure(pattern, false, true)?;
1108 }
1109 _ => self.compile_statement(stmt)?,
1110 }
1111 Ok(())
1112 }
1113
1114 fn name_idx(&mut self, name: &Arc<str>) -> u16 {
1115 self.chunk.add_name(Arc::clone(name))
1116 }
1117
1118 fn constant_idx(&mut self, c: Constant) -> u16 {
1119 self.chunk.add_constant(c)
1120 }
1121
1122 fn emit(&mut self, op: Opcode) {
1123 self.chunk.write_u8(op as u8);
1124 }
1125
1126 fn mark_line(&mut self, span: tishlang_ast::Span) {
1129 let offset = self.chunk.code.len();
1130 self.chunk.mark_line(offset, span.start.0 as u32);
1131 }
1132
1133 fn emit_u8(&mut self, op: Opcode, v: u8) {
1134 self.chunk.write_u8(op as u8);
1135 self.chunk.write_u16(v as u16);
1136 }
1137
1138 fn emit_u16(&mut self, op: Opcode, v: u16) {
1139 self.chunk.write_u8(op as u8);
1140 self.chunk.write_u16(v);
1141 }
1142
1143 fn emit_jump(&mut self, op: Opcode) -> usize {
1144 let pos = self.chunk.code.len();
1145 self.chunk.write_u8(op as u8);
1146 self.chunk.write_u16(0); pos + 1
1148 }
1149
1150 fn emit_jump_back(&mut self) -> usize {
1152 let pos = self.chunk.code.len();
1153 self.chunk.write_u8(Opcode::JumpBack as u8);
1154 self.chunk.write_u16(0);
1155 pos + 1
1156 }
1157
1158 fn patch_jump(&mut self, patch_pos: usize, target: usize) {
1159 let base = patch_pos + 2;
1160 let jump_offset = (target as i32).wrapping_sub(base as i32);
1161 let bytes = (jump_offset as i16).to_be_bytes();
1162 self.chunk.code[patch_pos] = bytes[0];
1163 self.chunk.code[patch_pos + 1] = bytes[1];
1164 }
1165
1166 fn compile_condition_jump_if_false(&mut self, cond: &Expr) -> Result<Vec<usize>, CompileError> {
1173 match cond {
1174 Expr::Binary {
1177 left,
1178 op: BinOp::And,
1179 right,
1180 ..
1181 } => {
1182 let mut patches = self.compile_condition_jump_if_false(left)?;
1183 patches.extend(self.compile_condition_jump_if_false(right)?);
1184 Ok(patches)
1185 }
1186 Expr::Binary {
1189 left,
1190 op: BinOp::Or,
1191 right,
1192 ..
1193 } => {
1194 self.compile_expr(left)?;
1195 let take_right = self.emit_jump(Opcode::JumpIfFalse); let done = self.emit_jump(Opcode::Jump); self.patch_jump(take_right, self.chunk.code.len());
1198 let patches = self.compile_condition_jump_if_false(right)?;
1199 self.patch_jump(done, self.chunk.code.len());
1200 Ok(patches)
1201 }
1202 _ => {
1204 self.compile_expr(cond)?;
1205 Ok(vec![self.emit_jump(Opcode::JumpIfFalse)])
1206 }
1207 }
1208 }
1209
1210 fn patch_jump_back(&mut self, patch_pos: usize, target: usize) {
1213 let after_insn = patch_pos + 2;
1214 let dist = after_insn.saturating_sub(target);
1215 let bytes = (dist as u16).to_be_bytes();
1216 self.chunk.code[patch_pos] = bytes[0];
1217 self.chunk.code[patch_pos + 1] = bytes[1];
1218 }
1219
1220 fn detect_property_sort_comparator(expr: &Expr) -> Option<(Arc<str>, bool)> {
1223 if let Expr::ArrowFunction { params, body, .. } = expr {
1224 if params.len() != 2 {
1225 return None;
1226 }
1227 let (param_a, param_b) = match (¶ms[0], ¶ms[1]) {
1228 (FunParam::Simple(a), FunParam::Simple(b))
1229 if a.default.is_none() && b.default.is_none() =>
1230 {
1231 (a.name.as_ref(), b.name.as_ref())
1232 }
1233 _ => return None,
1234 };
1235 let body_expr = match body {
1236 ArrowBody::Expr(e) => e.as_ref(),
1237 ArrowBody::Block(stmt) => {
1238 if let Statement::ExprStmt { expr: e, .. } = stmt.as_ref() {
1239 e
1240 } else {
1241 return None;
1242 }
1243 }
1244 };
1245 if let Expr::Binary {
1246 left,
1247 op: BinOp::Sub,
1248 right,
1249 ..
1250 } = body_expr
1251 {
1252 if let (
1253 Expr::Member {
1254 object: lo,
1255 prop: MemberProp::Name { name: p, .. },
1256 ..
1257 },
1258 Expr::Member {
1259 object: ro,
1260 prop: MemberProp::Name { name: pr, .. },
1261 ..
1262 },
1263 ) = (left.as_ref(), right.as_ref())
1264 {
1265 if p != pr {
1266 return None;
1267 }
1268 if let (Expr::Ident { name: ln, .. }, Expr::Ident { name: rn, .. }) =
1269 (lo.as_ref(), ro.as_ref())
1270 {
1271 if ln.as_ref() == param_a && rn.as_ref() == param_b {
1272 return Some((Arc::clone(p), true));
1273 }
1274 if ln.as_ref() == param_b && rn.as_ref() == param_a {
1275 return Some((Arc::clone(p), false));
1276 }
1277 }
1278 }
1279 }
1280 }
1281 None
1282 }
1283
1284 fn detect_numeric_sort_comparator(expr: &Expr) -> Option<bool> {
1286 if let Expr::ArrowFunction { params, body, .. } = expr {
1287 if params.len() != 2 {
1288 return None;
1289 }
1290 let (param_a, param_b) = match (¶ms[0], ¶ms[1]) {
1291 (FunParam::Simple(a), FunParam::Simple(b))
1292 if a.default.is_none() && b.default.is_none() =>
1293 {
1294 (a.name.as_ref(), b.name.as_ref())
1295 }
1296 _ => return None,
1297 };
1298 let body_expr = match body {
1299 ArrowBody::Expr(e) => e.as_ref(),
1300 ArrowBody::Block(stmt) => {
1301 if let Statement::ExprStmt { expr: e, .. } = stmt.as_ref() {
1302 e
1303 } else {
1304 return None;
1305 }
1306 }
1307 };
1308 if let Expr::Binary {
1309 left,
1310 op: BinOp::Sub,
1311 right,
1312 ..
1313 } = body_expr
1314 {
1315 if let (
1316 Expr::Ident {
1317 name: left_name, ..
1318 },
1319 Expr::Ident {
1320 name: right_name, ..
1321 },
1322 ) = (left.as_ref(), right.as_ref())
1323 {
1324 if left_name.as_ref() == param_a && right_name.as_ref() == param_b {
1325 return Some(true);
1326 }
1327 if left_name.as_ref() == param_b && right_name.as_ref() == param_a {
1328 return Some(false);
1329 }
1330 }
1331 }
1332 }
1333 None
1334 }
1335
1336 fn detect_simple_map_callback(expr: &Expr) -> Option<SimpleMapResult> {
1339 let (params, body) = match expr {
1340 Expr::ArrowFunction { params, body, .. } => (params, body),
1341 _ => return None,
1342 };
1343 if params.len() != 1 {
1344 return None;
1345 }
1346 let param_name = match ¶ms[0] {
1347 FunParam::Simple(tp) if tp.default.is_none() => tp.name.as_ref(),
1348 _ => return None,
1349 };
1350 let expr_ref: &Expr = match body {
1351 ArrowBody::Expr(e) => e.as_ref(),
1352 ArrowBody::Block(stmt) => {
1353 let s = stmt.as_ref();
1354 if let Statement::Return {
1355 value: Some(ref e), ..
1356 } = s
1357 {
1358 e
1359 } else if let Statement::ExprStmt { expr: ref e, .. } = s {
1360 e
1361 } else {
1362 return None;
1363 }
1364 }
1365 };
1366 if let Expr::Ident { name, .. } = expr_ref {
1368 if name.as_ref() == param_name {
1369 return Some(SimpleMapResult::Identity);
1370 }
1371 }
1372 if let Expr::Binary {
1374 left, op, right, ..
1375 } = expr_ref
1376 {
1377 let left_is_param =
1378 matches!(left.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
1379 let right_is_param =
1380 matches!(right.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
1381 let left_is_literal = matches!(left.as_ref(), Expr::Literal { .. });
1382 let right_is_literal = matches!(right.as_ref(), Expr::Literal { .. });
1383 if left_is_param && right_is_literal {
1384 if let Some(c) = literal_to_constant(right.as_ref()) {
1385 return Some(SimpleMapResult::BinOp(*op, c, true));
1386 }
1387 }
1388 if left_is_literal && right_is_param {
1389 if let Some(c) = literal_to_constant(left.as_ref()) {
1390 return Some(SimpleMapResult::BinOp(*op, c, false));
1391 }
1392 }
1393 }
1394 None
1395 }
1396
1397 fn detect_simple_filter_callback(expr: &Expr) -> Option<(BinOp, Constant, bool)> {
1399 let (params, body) = match expr {
1400 Expr::ArrowFunction { params, body, .. } => (params, body),
1401 _ => return None,
1402 };
1403 if params.len() != 1 {
1404 return None;
1405 }
1406 let param_name = match ¶ms[0] {
1407 FunParam::Simple(tp) if tp.default.is_none() => tp.name.as_ref(),
1408 _ => return None,
1409 };
1410 let expr_ref: &Expr = match body {
1411 ArrowBody::Expr(e) => e.as_ref(),
1412 ArrowBody::Block(stmt) => {
1413 let s = stmt.as_ref();
1414 if let Statement::Return {
1415 value: Some(ref e), ..
1416 } = s
1417 {
1418 e
1419 } else if let Statement::ExprStmt { expr: ref e, .. } = s {
1420 e
1421 } else {
1422 return None;
1423 }
1424 }
1425 };
1426 if let Expr::Binary {
1427 left, op, right, ..
1428 } = expr_ref
1429 {
1430 if !matches!(
1431 op,
1432 BinOp::Eq
1433 | BinOp::Ne
1434 | BinOp::StrictEq
1435 | BinOp::StrictNe
1436 | BinOp::Lt
1437 | BinOp::Le
1438 | BinOp::Gt
1439 | BinOp::Ge
1440 | BinOp::And
1441 | BinOp::Or
1442 ) {
1443 return None;
1444 }
1445 let left_is_param =
1446 matches!(left.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
1447 let right_is_param =
1448 matches!(right.as_ref(), Expr::Ident { name, .. } if name.as_ref() == param_name);
1449 let left_is_literal = matches!(left.as_ref(), Expr::Literal { .. });
1450 let right_is_literal = matches!(right.as_ref(), Expr::Literal { .. });
1451 if left_is_param && right_is_literal {
1452 if let Some(c) = literal_to_constant(right.as_ref()) {
1453 return Some((*op, c, true));
1454 }
1455 }
1456 if left_is_literal && right_is_param {
1457 if let Some(c) = literal_to_constant(left.as_ref()) {
1458 return Some((*op, c, false));
1459 }
1460 }
1461 }
1462 None
1463 }
1464
1465 fn compile_program(&mut self, program: &Program) -> Result<(), CompileError> {
1466 let stmts = &program.statements;
1467 if !self.retain_last_expr {
1471 if let Some(cap) = slot_analyze_toplevel(stmts) {
1472 self.general_slots = true;
1473 self.slot_captured = cap;
1474 self.slot_scopes.push(HashMap::default());
1475 }
1476 }
1477 let last_is_expr = self.retain_last_expr
1478 && stmts
1479 .last()
1480 .map(|s| matches!(s, Statement::ExprStmt { .. }))
1481 .unwrap_or(false);
1482
1483 if last_is_expr {
1484 let (rest, last) = stmts.split_at(stmts.len().saturating_sub(1));
1485 for stmt in rest {
1486 self.compile_statement(stmt)?;
1487 }
1488 if let Some(Statement::ExprStmt { expr, .. }) = last.first() {
1489 self.compile_expr(expr)?;
1490 }
1491 } else {
1492 for stmt in stmts {
1493 self.compile_statement(stmt)?;
1494 }
1495 let idx = self.constant_idx(Constant::Null);
1496 self.emit(Opcode::LoadConst);
1497 self.chunk.write_u16(idx);
1498 }
1499 if self.general_slots && self.next_slot > 0 {
1501 self.chunk.slot_based = true;
1502 self.chunk.num_slots = self.next_slot;
1503 }
1504 Ok(())
1505 }
1506
1507 fn compile_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
1508 self.mark_line(stmt.span());
1509 match stmt {
1510 Statement::Block { statements, .. } => {
1511 self.emit(Opcode::EnterBlock);
1512 self.block_depth += 1;
1513 self.enter_block_scope();
1514 for s in statements {
1515 self.compile_statement(s)?;
1516 }
1517 self.exit_block_scope();
1518 self.emit(Opcode::ExitBlock);
1519 self.block_depth -= 1;
1520 }
1521 Statement::Multi { statements, .. } => {
1524 for s in statements {
1525 self.compile_statement(s)?;
1526 }
1527 }
1528 Statement::VarDecl {
1529 name,
1530 init,
1531 mutable: _,
1532 ..
1533 } => {
1534 if let Some(expr) = init {
1535 self.compile_expr(expr)?;
1536 } else {
1537 let idx = self.constant_idx(Constant::Null);
1538 self.emit(Opcode::LoadConst);
1539 self.chunk.write_u16(idx);
1540 }
1541 if self.general_slots && !self.slot_captured.contains(name.as_ref()) {
1542 let slot = self.declare_slot(name);
1544 self.emit_u16(Opcode::StoreLocal, slot);
1545 } else {
1546 let idx = self.name_idx(name);
1547 self.emit_u16(Opcode::DeclareVar, idx);
1548 self.scope
1549 .last_mut()
1550 .unwrap()
1551 .insert(Arc::clone(name), false);
1552 }
1553 }
1554 Statement::VarDeclDestructure { pattern, init, .. } => {
1555 self.compile_expr(init)?;
1556 self.compile_destructure(pattern, false, false)?;
1557 }
1558 Statement::ExprStmt { expr, .. } => {
1559 if let Expr::CompoundAssign {
1565 name,
1566 op: CompoundOp::Add,
1567 value,
1568 ..
1569 } = expr
1570 {
1571 if let Some(slot) = self.resolve_slot(name) {
1572 self.compile_expr(value)?;
1573 self.emit_u16(Opcode::AppendLocal, slot);
1574 return Ok(());
1575 }
1576 }
1577 if let Expr::Assign { name, value, .. } = expr {
1583 if let Expr::Binary {
1584 left,
1585 op: BinOp::Add,
1586 right,
1587 ..
1588 } = value.as_ref()
1589 {
1590 if matches!(left.as_ref(), Expr::Ident { name: ln, .. } if ln == name)
1591 && is_string_typed(right)
1592 {
1593 if let Some(slot) = self.resolve_slot(name) {
1594 self.compile_expr(right)?;
1595 self.emit_u16(Opcode::AppendLocal, slot);
1596 return Ok(());
1597 }
1598 }
1599 }
1600 }
1601 self.compile_expr(expr)?;
1602 self.emit(Opcode::Pop);
1603 }
1604 Statement::If {
1605 cond,
1606 then_branch,
1607 else_branch,
1608 ..
1609 } => {
1610 let jump_else_sites = self.compile_condition_jump_if_false(cond)?;
1611 self.compile_statement(then_branch)?;
1612 let jump_end = self.emit_jump(Opcode::Jump);
1613 let else_target = self.chunk.code.len();
1614 for site in &jump_else_sites {
1615 self.patch_jump(*site, else_target);
1616 }
1617 if let Some(else_s) = else_branch {
1618 self.compile_statement(else_s)?;
1619 }
1620 self.patch_jump(jump_end, self.chunk.code.len());
1621 }
1622 Statement::While { cond, body, .. } => {
1623 let body_lets = Self::loop_body_block_lets(body);
1627 for n in &body_lets {
1628 let idx = self.name_idx(n);
1629 self.emit_u16(Opcode::LoopVarsBegin, idx);
1630 }
1631 let start = self.chunk.code.len();
1632 self.loop_stack.push(LoopInfo {
1633 break_patches: Vec::new(),
1634 continue_patches: Vec::new(),
1635 continue_is_forward_jump: false,
1636 });
1637 self.breakable_stack.push(Breakable::Loop {
1638 unwind_depth: self.block_depth,
1639 });
1640 let jump_out_sites = self.compile_condition_jump_if_false(cond)?;
1643 self.compile_statement(body)?;
1644 let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(start);
1645 self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
1646 let end = self.chunk.code.len();
1647 for site in &jump_out_sites {
1648 self.patch_jump(*site, end);
1649 }
1650 let info = self.loop_stack.pop().unwrap();
1651 self.breakable_stack.pop();
1652 for p in info.continue_patches {
1653 self.patch_jump_back(p, start);
1654 }
1655 for p in info.break_patches {
1656 self.patch_jump(p, end);
1657 }
1658 for _ in &body_lets {
1659 self.emit(Opcode::LoopVarsEnd);
1660 }
1661 }
1662 Statement::For {
1663 init,
1664 cond,
1665 update,
1666 body,
1667 ..
1668 } => {
1669 self.enter_block_scope();
1670 if let Some(i) = init {
1671 self.compile_for_init_statement(i.as_ref())?;
1672 }
1673 let loop_var: Option<Arc<str>> = match init.as_deref() {
1678 Some(Statement::VarDecl { name, .. }) => Some(Arc::clone(name)),
1679 _ => None,
1680 };
1681 if let Some(ref n) = loop_var {
1682 let idx = self.name_idx(n);
1683 self.emit_u16(Opcode::LoopVarsBegin, idx);
1684 }
1685 let cond_start = self.chunk.code.len();
1686 let jump_out_sites = if let Some(c) = cond {
1689 self.compile_condition_jump_if_false(c)?
1690 } else {
1691 let idx = self.constant_idx(Constant::Bool(true));
1692 self.emit(Opcode::LoadConst);
1693 self.chunk.write_u16(idx);
1694 vec![self.emit_jump(Opcode::JumpIfFalse)]
1695 };
1696 self.loop_stack.push(LoopInfo {
1697 break_patches: Vec::new(),
1698 continue_patches: Vec::new(),
1699 continue_is_forward_jump: true,
1700 });
1701 self.breakable_stack.push(Breakable::Loop {
1702 unwind_depth: self.block_depth,
1703 });
1704 self.compile_statement(body)?;
1705 let update_start = self.chunk.code.len();
1706 if let Some(u) = update {
1707 self.compile_expr(u)?;
1708 self.emit(Opcode::Pop);
1709 }
1710 let info = self.loop_stack.pop().unwrap();
1711 for p in info.continue_patches {
1712 self.patch_jump(p, update_start);
1713 }
1714 let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(cond_start);
1715 self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
1716 let end = self.chunk.code.len();
1717 for site in &jump_out_sites {
1718 self.patch_jump(*site, end);
1719 }
1720 for p in info.break_patches {
1721 self.patch_jump(p, end);
1722 }
1723 if loop_var.is_some() {
1726 self.emit(Opcode::LoopVarsEnd);
1727 }
1728 self.breakable_stack.pop();
1729 self.exit_block_scope();
1730 }
1731 Statement::ForOf {
1732 name,
1733 iterable,
1734 body,
1735 ..
1736 } => {
1737 self.compile_expr(iterable)?;
1738 self.emit(Opcode::IterNormalize);
1741 self.enter_block_scope();
1742 let arr_name = Arc::from("__forof_arr__");
1743 let i_name = Arc::from("__forof_i__");
1744 let len_name = Arc::from("__forof_len__");
1745 let arr_idx = self.name_idx(&arr_name);
1746 let i_idx = self.name_idx(&i_name);
1747 let len_idx = self.name_idx(&len_name);
1748 let name_idx = self.name_idx(name);
1749 self.emit_u16(Opcode::DeclareVar, arr_idx);
1750 self.scope
1751 .last_mut()
1752 .unwrap()
1753 .insert(arr_name.clone(), false);
1754 self.emit_u16(Opcode::LoadVar, arr_idx);
1755 let len_name_idx = self.name_idx(&Arc::from("length"));
1756 self.emit_u16(Opcode::GetMember, len_name_idx);
1757 self.emit_u16(Opcode::DeclareVar, len_idx);
1758 self.scope
1759 .last_mut()
1760 .unwrap()
1761 .insert(len_name.clone(), false);
1762 let zero_idx = self.constant_idx(Constant::Number(0.0));
1763 self.emit(Opcode::LoadConst);
1764 self.chunk.write_u16(zero_idx);
1765 self.emit_u16(Opcode::DeclareVar, i_idx);
1766 self.scope.last_mut().unwrap().insert(i_name.clone(), false);
1767 self.emit_u16(Opcode::LoopVarsBegin, name_idx);
1770 let cond_start = self.chunk.code.len();
1774 self.emit_u16(Opcode::LoadVar, i_idx);
1775 self.emit_u16(Opcode::LoadVar, len_idx);
1776 self.emit_u8(Opcode::BinOp, 10);
1777 let jump_out = self.emit_jump(Opcode::JumpIfFalse);
1778 self.loop_stack.push(LoopInfo {
1779 break_patches: Vec::new(),
1780 continue_patches: Vec::new(),
1781 continue_is_forward_jump: true,
1782 });
1783 self.breakable_stack.push(Breakable::Loop {
1784 unwind_depth: self.block_depth,
1785 });
1786 self.emit_u16(Opcode::LoadVar, arr_idx);
1787 self.emit_u16(Opcode::LoadVar, i_idx);
1788 self.emit(Opcode::GetIndex);
1789 self.emit_u16(Opcode::DeclareVar, name_idx);
1790 self.scope
1791 .last_mut()
1792 .unwrap()
1793 .insert(Arc::clone(name), false);
1794 self.compile_statement(body)?;
1795 let update_start = self.chunk.code.len();
1797 self.emit_u16(Opcode::LoadVar, i_idx);
1798 let one_idx = self.constant_idx(Constant::Number(1.0));
1799 self.emit(Opcode::LoadConst);
1800 self.chunk.write_u16(one_idx);
1801 self.emit_u8(Opcode::BinOp, 0);
1802 self.emit_u16(Opcode::StoreVar, i_idx);
1803 let info = self.loop_stack.pop().unwrap();
1804 self.breakable_stack.pop();
1805 for p in info.continue_patches {
1806 self.patch_jump(p, update_start);
1807 }
1808 let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(cond_start);
1809 self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
1810 let end = self.chunk.code.len();
1811 self.patch_jump(jump_out, end);
1812 for p in info.break_patches {
1813 self.patch_jump(p, end);
1814 }
1815 self.emit(Opcode::LoopVarsEnd);
1816 self.exit_block_scope();
1817 }
1818 Statement::ForIn {
1819 name,
1820 name_span,
1821 object,
1822 body,
1823 span,
1824 } => {
1825 let dummy = Span {
1830 start: (0, 0),
1831 end: (0, 0),
1832 };
1833 let keys_call = Expr::Call {
1834 callee: Box::new(Expr::Member {
1835 object: Box::new(Expr::Ident {
1836 name: Arc::from("Object"),
1837 span: dummy,
1838 }),
1839 prop: MemberProp::Name {
1840 name: Arc::from("keys"),
1841 span: dummy,
1842 },
1843 optional: false,
1844 span: dummy,
1845 }),
1846 args: vec![CallArg::Expr(object.clone())],
1847 span: dummy,
1848 };
1849 let lowered = Statement::ForOf {
1850 name: Arc::clone(name),
1851 name_span: *name_span,
1852 iterable: keys_call,
1853 body: body.clone(),
1854 span: *span,
1855 };
1856 self.compile_statement(&lowered)?;
1857 }
1858 Statement::Return { value, .. } => {
1859 if let Some(v) = value {
1862 self.compile_expr(v)?;
1863 } else {
1864 let idx = self.constant_idx(Constant::Null);
1865 self.emit(Opcode::LoadConst);
1866 self.chunk.write_u16(idx);
1867 }
1868 self.emit_pending_finallys()?;
1869 self.emit(Opcode::Return);
1870 }
1871 Statement::Break { .. } => {
1872 let unwind_depth = match self.breakable_stack.last() {
1873 Some(Breakable::Loop { unwind_depth })
1874 | Some(Breakable::Switch { unwind_depth }) => *unwind_depth,
1875 None => {
1876 return Err(CompileError {
1877 message: "break not inside a loop or switch".to_string(),
1878 });
1879 }
1880 };
1881 self.emit_exit_blocks_until_depth(unwind_depth);
1882 let pos = self.emit_jump(Opcode::Jump);
1883 match self.breakable_stack.last() {
1884 Some(Breakable::Loop { .. }) => {
1885 self.loop_stack.last_mut().unwrap().break_patches.push(pos);
1886 }
1887 Some(Breakable::Switch { .. }) => {
1888 self.switch_stack
1889 .last_mut()
1890 .unwrap()
1891 .break_patches
1892 .push(pos);
1893 }
1894 None => {}
1895 }
1896 }
1897 Statement::Continue { .. } => {
1898 let unwind_depth = self
1899 .breakable_stack
1900 .iter()
1901 .rev()
1902 .find_map(|b| match b {
1903 Breakable::Loop { unwind_depth } => Some(*unwind_depth),
1904 Breakable::Switch { .. } => None,
1905 })
1906 .ok_or_else(|| CompileError {
1907 message: "continue not inside a loop".to_string(),
1908 })?;
1909 self.emit_exit_blocks_until_depth(unwind_depth);
1910 let forward = self
1911 .loop_stack
1912 .last()
1913 .expect("continue not inside a loop")
1914 .continue_is_forward_jump;
1915 let pos = if forward {
1916 self.emit_jump(Opcode::Jump)
1917 } else {
1918 self.emit_jump_back()
1919 };
1920 self.loop_stack
1921 .last_mut()
1922 .expect("continue not inside a loop")
1923 .continue_patches
1924 .push(pos);
1925 }
1926 Statement::FunDecl {
1927 name,
1928 params,
1929 body,
1930 rest_param,
1931 async_: _,
1932 ..
1933 } => {
1934 let formal_len = params.len();
1935 let (mut param_names, slots) = Self::plan_function_params(params)?;
1936 let simple_slots = simple_fn_slots(params, rest_param.is_some(), |pset| {
1937 stmt_is_param_only(body, pset)
1938 });
1939 let captured = if simple_slots.is_none() {
1943 slot_analyze(params, rest_param.is_some(), body)
1944 } else {
1945 None
1946 };
1947 let mut inner = Chunk::new();
1948 inner.source = self.chunk.source.clone(); if let Some(rp) = rest_param {
1950 param_names.push(Arc::clone(&rp.name));
1951 inner.rest_param_index = (param_names.len() as u16).saturating_sub(1);
1952 }
1953 for p in ¶m_names {
1954 inner.add_name(Arc::clone(p));
1955 }
1956 inner.param_count = param_names.len() as u16;
1957 if simple_slots.is_some() {
1958 inner.slot_based = true;
1959 inner.num_slots = param_names.len() as u16;
1960 }
1961 if self.stable_globals.contains(name) {
1967 inner.global_name = Some(Arc::clone(name));
1968 }
1969 let mut inner_comp = Compiler::new(&mut inner, false);
1970 inner_comp.stable_globals = Arc::clone(&self.stable_globals);
1971 if !params_bind_name(params, name.as_ref()) && !stmt_rebinds(body, name.as_ref()) {
1976 inner_comp.self_fn_name = Some(Arc::clone(name));
1977 }
1978 let mut general_frame_slots: Option<u16> = None;
1979 if let Some(map) = simple_slots {
1980 inner_comp.slot_ctx = Some(map);
1981 inner_comp.emit_param_defaults_prologue(params)?;
1982 inner_comp.compile_statement(body)?;
1983 } else if let Some(cap) = captured {
1984 inner_comp.general_slots = true;
1988 inner_comp.slot_captured = cap;
1989 inner_comp.slot_scopes.push(HashMap::new());
1990 for p in ¶m_names {
1991 inner_comp.declare_slot(p);
1992 }
1993 inner_comp.emit_param_defaults_prologue(params)?;
1994 inner_comp.compile_statement(body)?;
1995 general_frame_slots = Some(inner_comp.next_slot);
1996 } else {
1997 inner_comp.scope = vec![param_names
1998 .iter()
1999 .map(|n| (Arc::clone(n), false))
2000 .collect::<HashMap<_, _>>()];
2001 inner_comp
2002 .emit_param_destructure_prologue(¶m_names[..formal_len], &slots)?;
2003 inner_comp.emit_param_defaults_prologue(params)?;
2004 inner_comp.compile_statement(body)?;
2005 }
2006 inner_comp.emit(Opcode::LoadConst);
2007 let idx = inner_comp.constant_idx(Constant::Null);
2008 inner_comp.chunk.write_u16(idx);
2009 inner_comp.emit(Opcode::Return);
2010 if let Some(n) = general_frame_slots {
2011 inner_comp.chunk.slot_based = true;
2012 inner_comp.chunk.num_slots = n;
2013 }
2014 let nested_idx = self.chunk.add_nested(inner);
2015 self.emit(Opcode::LoadConst);
2016 let idx = self.constant_idx(Constant::Closure(nested_idx));
2017 self.chunk.write_u16(idx);
2018 let idx = self.name_idx(name);
2019 self.emit_u16(Opcode::DeclareVar, idx);
2020 self.scope
2021 .last_mut()
2022 .unwrap()
2023 .insert(Arc::clone(name), false);
2024 }
2025 Statement::DoWhile { body, cond, .. } => {
2026 let body_lets = Self::loop_body_block_lets(body);
2027 for n in &body_lets {
2028 let idx = self.name_idx(n);
2029 self.emit_u16(Opcode::LoopVarsBegin, idx);
2030 }
2031 let start = self.chunk.code.len();
2032 self.loop_stack.push(LoopInfo {
2033 break_patches: Vec::new(),
2034 continue_patches: Vec::new(),
2035 continue_is_forward_jump: true,
2041 });
2042 self.breakable_stack.push(Breakable::Loop {
2043 unwind_depth: self.block_depth,
2044 });
2045 self.compile_statement(body)?;
2046 let cond_start = self.chunk.code.len();
2047 let exit_sites = self.compile_condition_jump_if_false(cond)?;
2050 let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(start);
2051 self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
2052 let end = self.chunk.code.len();
2053 for site in &exit_sites {
2054 self.patch_jump(*site, end);
2055 }
2056 let info = self.loop_stack.pop().unwrap();
2057 self.breakable_stack.pop();
2058 for p in info.continue_patches {
2059 self.patch_jump(p, cond_start);
2061 }
2062 for p in info.break_patches {
2063 self.patch_jump(p, end);
2064 }
2065 for _ in &body_lets {
2066 self.emit(Opcode::LoopVarsEnd);
2067 }
2068 }
2069 Statement::Switch {
2070 expr,
2071 cases,
2072 default_body,
2073 ..
2074 } => {
2075 let switch_unwind_depth = self.block_depth;
2076 self.switch_stack.push(SwitchInfo {
2077 break_patches: Vec::new(),
2078 });
2079 self.breakable_stack.push(Breakable::Switch {
2080 unwind_depth: switch_unwind_depth,
2081 });
2082 self.compile_expr(expr)?;
2083 self.emit(Opcode::Dup);
2084 let mut end_patches = Vec::new();
2085 for (case_expr, case_body) in cases {
2086 self.emit(Opcode::Dup);
2087 if let Some(ce) = case_expr {
2088 self.compile_expr(ce)?;
2089 self.emit_u8(Opcode::BinOp, 8);
2090 let jump_next = self.emit_jump(Opcode::JumpIfFalse);
2091 self.compile_statement(&Statement::Block {
2093 statements: case_body.clone(),
2094 span: Span {
2095 start: (0, 0),
2096 end: (0, 0),
2097 },
2098 })?;
2099 let jump_end = self.emit_jump(Opcode::Jump);
2100 end_patches.push(jump_end);
2101 self.patch_jump(jump_next, self.chunk.code.len());
2102 } else {
2103 self.emit(Opcode::Pop);
2104 self.compile_statement(&Statement::Block {
2105 statements: case_body.clone(),
2106 span: Span {
2107 start: (0, 0),
2108 end: (0, 0),
2109 },
2110 })?;
2111 }
2112 }
2113 if let Some(body) = default_body {
2114 self.emit(Opcode::Pop);
2115 self.compile_statement(&Statement::Block {
2116 statements: body.clone(),
2117 span: Span {
2118 start: (0, 0),
2119 end: (0, 0),
2120 },
2121 })?;
2122 } else {
2123 self.emit(Opcode::Pop);
2124 }
2125 for p in end_patches {
2126 self.patch_jump(p, self.chunk.code.len());
2127 }
2128 let sw = self.switch_stack.pop().unwrap();
2129 self.breakable_stack.pop();
2130 for p in sw.break_patches {
2131 self.patch_jump(p, self.chunk.code.len());
2132 }
2133 }
2134 Statement::Throw { value, .. } => {
2135 self.compile_expr(value)?;
2136 self.emit(Opcode::Throw);
2137 }
2138 Statement::Try {
2139 body,
2140 catch_param,
2141 catch_body,
2142 finally_body,
2143 ..
2144 } => {
2145 let catch_offset_pos = self.chunk.code.len();
2146 self.emit(Opcode::EnterTry);
2147 self.chunk.write_u16(0);
2148 if let Some(f) = finally_body {
2150 self.finally_stack.push((**f).clone());
2151 }
2152 self.compile_statement(body)?;
2153 self.emit(Opcode::ExitTry);
2154 let jump_over_catch = self.emit_jump(Opcode::Jump);
2155 let catch_start = self.chunk.code.len();
2156 if let Some(catch_stmt) = catch_body {
2157 if let Some(param) = catch_param {
2158 self.emit(Opcode::EnterBlock);
2159 self.block_depth += 1;
2160 self.enter_block_scope();
2161 let param_idx = self.name_idx(param);
2162 self.emit_u16(Opcode::DeclareVar, param_idx);
2163 self.scope
2164 .last_mut()
2165 .unwrap()
2166 .insert(Arc::clone(param), false);
2167 self.compile_statement(catch_stmt)?;
2168 self.exit_block_scope();
2169 self.emit(Opcode::ExitBlock);
2170 self.block_depth -= 1;
2171 } else {
2172 self.emit(Opcode::Pop);
2173 self.compile_statement(catch_stmt)?;
2174 }
2175 } else {
2176 if let Some(f) = finally_body {
2178 self.compile_statement(f)?;
2179 }
2180 self.emit(Opcode::Throw);
2181 }
2182 let after_catch = self.chunk.code.len();
2183 self.patch_jump(jump_over_catch, after_catch);
2184 if finally_body.is_some() {
2187 self.finally_stack.pop();
2188 }
2189 if let Some(finally) = finally_body {
2190 self.compile_statement(finally)?;
2191 }
2192 let catch_offset =
2193 catch_start.wrapping_sub(catch_offset_pos).wrapping_sub(3) as u16;
2194 self.chunk.code[catch_offset_pos + 1] = (catch_offset >> 8) as u8;
2195 self.chunk.code[catch_offset_pos + 2] = (catch_offset & 0xff) as u8;
2196 }
2197 Statement::Import { .. } => {
2198 return Err(CompileError {
2199 message: "Import not supported in bytecode".to_string(),
2200 });
2201 }
2202 Statement::Export { declaration, .. } => match declaration.as_ref() {
2203 ExportDeclaration::Named(inner_stmt) => {
2204 self.compile_statement(inner_stmt.as_ref())?;
2205 }
2206 ExportDeclaration::Default(_) => {
2207 return Err(CompileError {
2208 message: "export default is not supported in bytecode".to_string(),
2209 });
2210 }
2211 ExportDeclaration::ReExport { .. } => {}
2212 },
2213 Statement::TypeAlias { .. }
2214 | Statement::DeclareVar { .. }
2215 | Statement::DeclareFun { .. } => {}
2216 }
2217 Ok(())
2218 }
2219
2220 fn compile_destructure(
2221 &mut self,
2222 pattern: &DestructPattern,
2223 mutable: bool,
2224 for_header_binding: bool,
2225 ) -> Result<(), CompileError> {
2226 let decl_op = if for_header_binding {
2227 Opcode::DeclareVarPlain
2228 } else {
2229 Opcode::DeclareVar
2230 };
2231 match pattern {
2232 DestructPattern::Array(elements) => {
2233 for (i, elem) in elements.iter().enumerate() {
2234 match elem {
2235 Some(DestructElement::Ident(name, _)) => {
2236 self.emit(Opcode::Dup);
2237 let idx = self.constant_idx(Constant::Number(i as f64));
2238 self.emit(Opcode::LoadConst);
2239 self.chunk.write_u16(idx);
2240 self.emit(Opcode::GetIndex);
2241 let idx = self.name_idx(name);
2242 self.emit_u16(decl_op, idx);
2243 self.scope
2244 .last_mut()
2245 .unwrap()
2246 .insert(Arc::clone(name), false);
2247 }
2248 None => {}
2250 Some(DestructElement::Pattern(sub)) => {
2254 self.emit(Opcode::Dup);
2255 let idx = self.constant_idx(Constant::Number(i as f64));
2256 self.emit(Opcode::LoadConst);
2257 self.chunk.write_u16(idx);
2258 self.emit(Opcode::GetIndex);
2259 self.compile_destructure(sub, mutable, for_header_binding)?;
2260 }
2261 Some(DestructElement::Rest(name, _)) => {
2265 self.emit(Opcode::Dup);
2266 let slice_idx = self.name_idx(&Arc::from("slice"));
2267 self.emit_u16(Opcode::GetMember, slice_idx);
2268 let idx = self.constant_idx(Constant::Number(i as f64));
2269 self.emit(Opcode::LoadConst);
2270 self.chunk.write_u16(idx);
2271 self.emit_u16(Opcode::Call, 1);
2272 let nidx = self.name_idx(name);
2273 self.emit_u16(decl_op, nidx);
2274 self.scope
2275 .last_mut()
2276 .unwrap()
2277 .insert(Arc::clone(name), false);
2278 }
2279 }
2280 }
2281 self.emit(Opcode::Pop);
2282 }
2283 DestructPattern::Object(props) => {
2284 for prop in props {
2285 self.emit(Opcode::Dup);
2286 let key_idx = self.constant_idx(Constant::String(Arc::clone(&prop.key)));
2287 self.emit(Opcode::LoadConst);
2288 self.chunk.write_u16(key_idx);
2289 self.emit(Opcode::GetIndex); match &prop.value {
2291 DestructElement::Ident(name, _) => {
2292 let idx = self.name_idx(name);
2293 self.emit_u16(decl_op, idx);
2294 if mutable {
2295 self.scope
2296 .last_mut()
2297 .unwrap()
2298 .insert(Arc::clone(name), false);
2299 }
2300 }
2301 DestructElement::Pattern(sub) => {
2304 self.compile_destructure(sub, mutable, for_header_binding)?;
2305 }
2306 DestructElement::Rest(_, _) => {
2308 return Err(CompileError {
2309 message: "Object rest destructuring not yet supported".to_string(),
2310 });
2311 }
2312 }
2313 }
2314 self.emit(Opcode::Pop);
2315 }
2316 }
2317 Ok(())
2318 }
2319
2320 fn compile_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
2321 self.mark_line(expr.span());
2322 match expr {
2323 Expr::Literal { value, .. } => {
2324 let c = match value {
2325 Literal::Number(n) => Constant::Number(*n),
2326 Literal::String(s) => Constant::String(Arc::clone(s)),
2327 Literal::Bool(b) => Constant::Bool(*b),
2328 Literal::Null => Constant::Null,
2329 };
2330 let idx = self.constant_idx(c);
2331 self.emit(Opcode::LoadConst);
2332 self.chunk.write_u16(idx);
2333 }
2334 Expr::Ident { name, .. } => {
2335 self.emit_var_load(name);
2337 }
2338 Expr::Binary {
2339 left, op, right, ..
2340 } => {
2341 match op {
2342 BinOp::And => {
2343 self.compile_expr(left)?;
2349 self.emit(Opcode::Dup);
2350 let jump_end = self.emit_jump(Opcode::JumpIfFalse); self.emit(Opcode::Pop); self.compile_expr(right)?; self.patch_jump(jump_end, self.chunk.code.len());
2354 }
2355 BinOp::Or => {
2356 self.compile_expr(left)?;
2358 self.emit(Opcode::Dup);
2359 let jump_eval_right = self.emit_jump(Opcode::JumpIfFalse);
2360 let jump_end = self.emit_jump(Opcode::Jump);
2361 self.patch_jump(jump_eval_right, self.chunk.code.len());
2362 self.emit(Opcode::Pop); self.compile_expr(right)?;
2364 self.patch_jump(jump_end, self.chunk.code.len());
2365 }
2366 _ => {
2367 self.compile_expr(left)?;
2368 self.compile_expr(right)?;
2369 self.emit_u8(Opcode::BinOp, binop_to_u8(*op));
2370 }
2371 }
2372 }
2373 Expr::Unary { op, operand, .. } => {
2374 self.compile_expr(operand)?;
2375 self.emit_u8(Opcode::UnaryOp, unaryop_to_u8(*op));
2376 }
2377 Expr::Call { callee, args, .. } => {
2378 if self.math_is_global && args.len() == 1 {
2382 if let Expr::Member {
2383 object,
2384 prop: MemberProp::Name { name: fname, .. },
2385 optional: false,
2386 ..
2387 } = callee.as_ref()
2388 {
2389 if let (Expr::Ident { name: obj, .. }, CallArg::Expr(arg)) =
2390 (object.as_ref(), &args[0])
2391 {
2392 if obj.as_ref() == "Math" && self.resolve_slot("Math").is_none() {
2393 if let Some(mfn) = MathUnaryFn::from_name(fname.as_ref()) {
2394 self.compile_expr(arg)?;
2395 self.emit_u16(Opcode::MathUnary, mfn as u16);
2396 return Ok(());
2397 }
2398 }
2399 }
2400 }
2401 }
2402 if !args.iter().any(|a| matches!(a, CallArg::Spread(_)))
2404 && args.len() == 1
2405 && matches!(args[0], CallArg::Expr(_))
2406 {
2407 if let (
2408 Expr::Member {
2409 object,
2410 prop: MemberProp::Name { name: key, .. },
2411 optional: false,
2412 ..
2413 },
2414 CallArg::Expr(cmp_expr),
2415 ) = (callee.as_ref(), &args[0])
2416 {
2417 if key.as_ref() == "sort" {
2418 if let Some(ascending) = Self::detect_numeric_sort_comparator(cmp_expr)
2419 {
2420 self.compile_expr(object)?;
2421 self.emit_u8(
2422 Opcode::ArraySortNumeric,
2423 if ascending { 0 } else { 1 },
2424 );
2425 return Ok(());
2426 }
2427 if let Some((prop, ascending)) =
2428 Self::detect_property_sort_comparator(cmp_expr)
2429 {
2430 self.compile_expr(object)?;
2431 let prop_idx = self.constant_idx(Constant::String(prop));
2432 self.emit(Opcode::ArraySortByProperty);
2433 self.chunk.write_u16(prop_idx);
2434 self.chunk.write_u16(if ascending { 0 } else { 1 });
2435 return Ok(());
2436 }
2437 }
2438 if key.as_ref() == "map" {
2439 if let Some(simple) = Self::detect_simple_map_callback(cmp_expr) {
2440 self.compile_expr(object)?;
2441 match simple {
2442 SimpleMapResult::Identity => {
2443 self.emit(Opcode::ArrayMapIdentity);
2444 }
2445 SimpleMapResult::BinOp(op, c, param_left) => {
2446 let const_idx = self.constant_idx(c);
2447 self.emit(Opcode::ArrayMapBinOp);
2448 self.chunk.write_u8(binop_to_u8(op));
2449 self.chunk.write_u16(const_idx);
2450 self.chunk.write_u8(if param_left { 0 } else { 1 });
2451 }
2452 }
2453 return Ok(());
2454 }
2455 }
2456 if key.as_ref() == "filter" {
2457 if let Some((op, const_val, param_left)) =
2458 Self::detect_simple_filter_callback(cmp_expr)
2459 {
2460 self.compile_expr(object)?;
2461 let const_idx = self.constant_idx(const_val);
2462 self.emit(Opcode::ArrayFilterBinOp);
2463 self.chunk.write_u8(binop_to_u8(op));
2464 self.chunk.write_u16(const_idx);
2465 self.chunk.write_u8(if param_left { 0 } else { 1 });
2466 return Ok(());
2467 }
2468 }
2469 }
2470 }
2471 let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
2472 if has_spread {
2473 self.emit_u16(Opcode::NewArray, 0);
2475 for arg in args {
2476 match arg {
2477 CallArg::Expr(e) => {
2478 self.compile_expr(e)?;
2479 self.emit_u16(Opcode::NewArray, 1);
2480 self.emit(Opcode::ConcatArray);
2481 }
2482 CallArg::Spread(expr) => {
2483 self.compile_expr(expr)?;
2484 self.emit(Opcode::ConcatArray);
2485 }
2486 }
2487 }
2488 self.compile_expr(callee)?;
2489 self.emit(Opcode::CallSpread);
2490 } else {
2491 let is_self_call = matches!(
2496 callee.as_ref(),
2497 Expr::Ident { name, .. } if self.self_fn_name.as_deref() == Some(name.as_ref())
2498 );
2499 if is_self_call {
2500 for arg in args {
2501 if let CallArg::Expr(e) = arg {
2502 self.compile_expr(e)?;
2503 }
2504 }
2505 self.emit_u16(Opcode::SelfCall, args.len() as u16);
2506 } else {
2507 self.compile_expr(callee)?;
2508 for arg in args {
2509 if let CallArg::Expr(e) = arg {
2510 self.compile_expr(e)?;
2511 }
2512 }
2513 self.emit_u16(Opcode::Call, args.len() as u16);
2514 }
2515 }
2516 }
2517 Expr::Member {
2518 object,
2519 prop,
2520 optional,
2521 ..
2522 } => {
2523 self.compile_expr(object)?;
2524 if *optional {
2525 self.emit(Opcode::Dup);
2526 let null_idx = self.constant_idx(Constant::Null);
2527 self.emit(Opcode::LoadConst);
2528 self.chunk.write_u16(null_idx);
2529 self.emit_u8(Opcode::BinOp, 8);
2530 let jump_to_null = self.emit_jump(Opcode::JumpIfFalse);
2531 let jump_to_get_instr = self.chunk.code.len();
2532 let jump_to_get = self.emit_jump(Opcode::Jump);
2533 self.patch_jump(jump_to_null, jump_to_get_instr);
2534 self.emit(Opcode::Pop);
2535 self.emit(Opcode::LoadConst);
2536 self.chunk.write_u16(null_idx);
2537 let jump_end = self.emit_jump(Opcode::Jump);
2538 self.patch_jump(jump_to_get, self.chunk.code.len());
2539 match prop {
2540 MemberProp::Name { name: key, .. } => {
2541 let idx = self.name_idx(key);
2542 self.emit_u16(Opcode::GetMemberOptional, idx);
2543 }
2544 MemberProp::Expr(e) => {
2545 self.compile_expr(e)?;
2546 self.emit(Opcode::GetIndex);
2547 }
2548 }
2549 self.patch_jump(jump_end, self.chunk.code.len());
2550 } else {
2551 match prop {
2552 MemberProp::Name { name: key, .. } => {
2553 let idx = self.name_idx(key);
2554 self.emit_u16(Opcode::GetMember, idx);
2555 }
2556 MemberProp::Expr(e) => {
2557 self.compile_expr(e)?;
2558 self.emit(Opcode::GetIndex);
2559 }
2560 }
2561 }
2562 }
2563 Expr::Index { object, index, .. } => {
2564 self.compile_expr(object)?;
2565 self.compile_expr(index)?;
2566 self.emit(Opcode::GetIndex);
2567 }
2568 Expr::Conditional {
2569 cond,
2570 then_branch,
2571 else_branch,
2572 ..
2573 } => {
2574 self.compile_expr(cond)?;
2575 let jump_else = self.emit_jump(Opcode::JumpIfFalse);
2576 self.compile_expr(then_branch)?;
2578 let jump_end = self.emit_jump(Opcode::Jump);
2579 self.patch_jump(jump_else, self.chunk.code.len());
2580 self.compile_expr(else_branch)?;
2582 self.patch_jump(jump_end, self.chunk.code.len());
2583 }
2584 Expr::NullishCoalesce { left, right, .. } => {
2585 self.compile_expr(left)?;
2586 self.emit(Opcode::Dup);
2587 let idx = self.constant_idx(Constant::Null);
2588 self.emit(Opcode::LoadConst);
2589 self.chunk.write_u16(idx);
2590 self.emit_u8(Opcode::BinOp, binop_to_u8(BinOp::StrictNe));
2591 let jump_to_right = self.emit_jump(Opcode::JumpIfFalse);
2592 let jump_end = self.emit_jump(Opcode::Jump);
2593 self.patch_jump(jump_to_right, self.chunk.code.len());
2594 self.emit(Opcode::Pop);
2595 self.compile_expr(right)?;
2596 self.patch_jump(jump_end, self.chunk.code.len());
2597 }
2598 Expr::Array { elements, .. } => {
2599 let has_spread = elements
2600 .iter()
2601 .any(|e| matches!(e, ArrayElement::Spread(_)));
2602 if has_spread {
2603 self.emit_u16(Opcode::NewArray, 0);
2605 for elem in elements {
2606 match elem {
2607 ArrayElement::Expr(e) => {
2608 self.compile_expr(e)?;
2609 self.emit_u16(Opcode::NewArray, 1);
2610 self.emit(Opcode::ConcatArray);
2611 }
2612 ArrayElement::Spread(expr) => {
2613 self.compile_expr(expr)?;
2614 self.emit(Opcode::ConcatArray);
2615 }
2616 }
2617 }
2618 } else {
2619 for elem in elements {
2620 if let ArrayElement::Expr(e) = elem {
2621 self.compile_expr(e)?;
2622 }
2623 }
2624 self.emit_u16(Opcode::NewArray, elements.len() as u16);
2625 }
2626 }
2627 Expr::Object { props, .. } => {
2628 let has_spread = props.iter().any(|p| matches!(p, ObjectProp::Spread(_)));
2629 if has_spread {
2630 self.emit_u16(Opcode::NewObject, 0); for prop in props {
2632 match prop {
2633 ObjectProp::KeyValue(k, v, _) => {
2634 let idx = self.constant_idx(Constant::String(Arc::clone(k)));
2635 self.emit(Opcode::LoadConst);
2636 self.chunk.write_u16(idx);
2637 self.compile_expr(v)?;
2638 self.emit_u16(Opcode::NewObject, 1);
2639 self.emit(Opcode::MergeObject);
2640 }
2641 ObjectProp::Spread(expr) => {
2642 self.compile_expr(expr)?;
2643 self.emit(Opcode::MergeObject);
2644 }
2645 }
2646 }
2647 } else {
2648 for prop in props {
2649 if let ObjectProp::KeyValue(k, v, _) = prop {
2650 let idx = self.constant_idx(Constant::String(Arc::clone(k)));
2651 self.emit(Opcode::LoadConst);
2652 self.chunk.write_u16(idx);
2653 self.compile_expr(v)?;
2654 }
2655 }
2656 self.emit_u16(Opcode::NewObject, props.len() as u16);
2657 }
2658 }
2659 Expr::Assign { name, value, .. } => {
2660 self.compile_expr(value)?;
2661 self.emit_var_store(name);
2662 self.emit_var_load(name); }
2664 Expr::TypeOf { operand, .. } => {
2665 let typeof_idx = self.name_idx(&Arc::from("typeof"));
2666 self.emit_u16(Opcode::LoadGlobal, typeof_idx);
2667 self.compile_expr(operand)?;
2668 self.emit_u16(Opcode::Call, 1);
2669 }
2670 Expr::ArrowFunction { params, body, .. } => {
2671 let formal_len = params.len();
2672 let (param_names, slots) = Self::plan_function_params(params)?;
2673 let simple_slots = simple_fn_slots(params, false, |pset| match body {
2674 ArrowBody::Expr(e) => expr_is_param_only(e, pset),
2675 ArrowBody::Block(s) => stmt_is_param_only(s, pset),
2676 });
2677 let mut inner = Chunk::new();
2678 inner.source = self.chunk.source.clone(); for p in ¶m_names {
2680 inner.add_name(Arc::clone(p));
2681 }
2682 inner.param_count = param_names.len() as u16;
2683 if simple_slots.is_some() {
2684 inner.slot_based = true;
2685 inner.num_slots = param_names.len() as u16;
2686 }
2687 let mut inner_comp = Compiler::new(&mut inner, false);
2688 inner_comp.stable_globals = Arc::clone(&self.stable_globals); if let Some(map) = simple_slots {
2690 inner_comp.slot_ctx = Some(map);
2691 } else {
2692 inner_comp.scope = vec![param_names
2693 .iter()
2694 .map(|n| (Arc::clone(n), false))
2695 .collect::<HashMap<_, _>>()];
2696 inner_comp
2697 .emit_param_destructure_prologue(¶m_names[..formal_len], &slots)?;
2698 }
2699 inner_comp.emit_param_defaults_prologue(params)?;
2700 match body {
2701 ArrowBody::Expr(e) => {
2702 inner_comp.compile_expr(e)?;
2703 inner_comp.emit(Opcode::Return);
2704 }
2705 ArrowBody::Block(s) => {
2706 inner_comp.compile_statement(s)?;
2707 let idx = inner_comp.constant_idx(Constant::Null);
2708 inner_comp.emit(Opcode::LoadConst);
2709 inner_comp.chunk.write_u16(idx);
2710 inner_comp.emit(Opcode::Return);
2711 }
2712 }
2713 let nested_idx = self.chunk.add_nested(inner);
2714 let idx = self.constant_idx(Constant::Closure(nested_idx));
2715 self.emit(Opcode::LoadConst);
2716 self.chunk.write_u16(idx);
2717 }
2718 Expr::TemplateLiteral { quasis, exprs, .. } => {
2719 if exprs.is_empty() {
2720 let s = quasis[0].to_string();
2721 let idx = self.constant_idx(Constant::String(Arc::from(s)));
2722 self.emit(Opcode::LoadConst);
2723 self.chunk.write_u16(idx);
2724 } else {
2725 let first = quasis[0].to_string();
2727 let idx = self.constant_idx(Constant::String(Arc::from(first)));
2728 self.emit(Opcode::LoadConst);
2729 self.chunk.write_u16(idx);
2730 for (i, expr) in exprs.iter().enumerate() {
2731 self.compile_expr(expr)?;
2732 self.emit_u8(Opcode::BinOp, 0); let quasi_s = quasis[i + 1].to_string();
2734 let qidx = self.constant_idx(Constant::String(Arc::from(quasi_s)));
2735 self.emit(Opcode::LoadConst);
2736 self.chunk.write_u16(qidx);
2737 self.emit_u8(Opcode::BinOp, 0); }
2739 }
2740 }
2741 Expr::PostfixInc { name, .. } => {
2742 let one = self.constant_idx(Constant::Number(1.0));
2743 self.emit_var_load(name);
2744 self.emit(Opcode::Dup);
2745 self.emit(Opcode::LoadConst);
2746 self.chunk.write_u16(one);
2747 self.emit_u8(Opcode::BinOp, 0);
2748 self.emit_var_store(name);
2749 }
2750 Expr::PostfixDec { name, .. } => {
2751 let one = self.constant_idx(Constant::Number(1.0));
2752 self.emit_var_load(name);
2753 self.emit(Opcode::Dup);
2754 self.emit(Opcode::LoadConst);
2755 self.chunk.write_u16(one);
2756 self.emit_u8(Opcode::BinOp, 1);
2757 self.emit_var_store(name);
2758 }
2759 Expr::PrefixInc { name, .. } => {
2760 let one = self.constant_idx(Constant::Number(1.0));
2761 self.emit_var_load(name);
2762 self.emit(Opcode::LoadConst);
2763 self.chunk.write_u16(one);
2764 self.emit_u8(Opcode::BinOp, 0);
2765 self.emit(Opcode::Dup);
2766 self.emit_var_store(name);
2767 }
2768 Expr::PrefixDec { name, .. } => {
2769 let one = self.constant_idx(Constant::Number(1.0));
2770 self.emit_var_load(name);
2771 self.emit(Opcode::LoadConst);
2772 self.chunk.write_u16(one);
2773 self.emit_u8(Opcode::BinOp, 1);
2774 self.emit(Opcode::Dup);
2775 self.emit_var_store(name);
2776 }
2777 Expr::CompoundAssign {
2778 name, op, value, ..
2779 } => {
2780 self.emit_var_load(name);
2781 self.compile_expr(value)?;
2782 self.emit_u8(Opcode::BinOp, compound_op_to_u8(*op));
2783 self.emit(Opcode::Dup);
2784 self.emit_var_store(name);
2785 }
2786 Expr::MemberAssign {
2787 object,
2788 prop,
2789 value,
2790 ..
2791 } => {
2792 self.compile_expr(object)?;
2793 self.compile_expr(value)?;
2794 let idx = self.name_idx(prop);
2795 self.emit_u16(Opcode::SetMember, idx); }
2797 Expr::IndexAssign {
2798 object,
2799 index,
2800 value,
2801 ..
2802 } => {
2803 self.compile_expr(object)?;
2804 self.compile_expr(index)?;
2805 self.compile_expr(value)?;
2806 self.emit(Opcode::Dup); self.emit(Opcode::SetIndex);
2808 }
2809 Expr::NativeModuleLoad {
2810 spec, export_name, ..
2811 } => {
2812 let spec_idx = self.constant_idx(Constant::String(Arc::clone(spec)));
2813 let export_idx = self.constant_idx(Constant::String(Arc::clone(export_name)));
2814 self.emit(Opcode::LoadNativeExport);
2815 self.chunk.write_u16(spec_idx);
2816 self.chunk.write_u16(export_idx);
2817 }
2818 Expr::JsxElement {
2819 tag,
2820 props,
2821 children,
2822 ..
2823 } => {
2824 self.compile_jsx_element(tag, props, children)?;
2825 }
2826 Expr::JsxFragment { children, .. } => {
2827 self.compile_jsx_fragment(children)?;
2828 }
2829 Expr::Await { operand, .. } => {
2830 self.compile_expr(operand)?;
2832 self.emit(Opcode::AwaitPromise);
2833 }
2834 Expr::Delete { target, .. } => {
2835 match target.as_ref() {
2839 Expr::Member {
2840 object,
2841 prop: MemberProp::Name { name, .. },
2842 ..
2843 } => {
2844 self.compile_expr(object)?;
2845 let idx = self.constant_idx(Constant::String(Arc::clone(name)));
2846 self.emit(Opcode::LoadConst);
2847 self.chunk.write_u16(idx);
2848 self.emit(Opcode::DeleteIndex);
2849 }
2850 Expr::Member {
2851 object,
2852 prop: MemberProp::Expr(key),
2853 ..
2854 } => {
2855 self.compile_expr(object)?;
2856 self.compile_expr(key)?;
2857 self.emit(Opcode::DeleteIndex);
2858 }
2859 Expr::Index { object, index, .. } => {
2860 self.compile_expr(object)?;
2861 self.compile_expr(index)?;
2862 self.emit(Opcode::DeleteIndex);
2863 }
2864 _ => {
2865 let idx = self.constant_idx(Constant::Bool(true));
2866 self.emit(Opcode::LoadConst);
2867 self.chunk.write_u16(idx);
2868 }
2869 }
2870 }
2871 Expr::LogicalAssign {
2872 name, op, value, ..
2873 } => {
2874 match op {
2875 LogicalAssignOp::OrOr => {
2876 self.emit_var_load(name);
2878 self.emit(Opcode::Dup);
2879 let j_rhs = self.emit_jump(Opcode::JumpIfFalse);
2880 let j_end = self.emit_jump(Opcode::Jump);
2881 self.patch_jump(j_rhs, self.chunk.code.len());
2882 self.emit(Opcode::Pop);
2883 self.compile_expr(value)?;
2884 self.emit_var_store(name);
2885 self.emit_var_load(name);
2886 let end = self.chunk.code.len();
2887 self.patch_jump(j_end, end);
2888 }
2889 LogicalAssignOp::AndAnd => {
2890 self.emit_var_load(name);
2892 self.emit(Opcode::Dup);
2893 let j_short = self.emit_jump(Opcode::JumpIfFalse);
2894 self.emit(Opcode::Pop);
2895 self.compile_expr(value)?;
2896 self.emit_var_store(name);
2897 self.emit_var_load(name);
2898 let j_end = self.emit_jump(Opcode::Jump);
2899 let end = self.chunk.code.len();
2900 self.patch_jump(j_short, end);
2901 self.patch_jump(j_end, end);
2902 }
2903 LogicalAssignOp::Nullish => {
2904 let null_c = self.constant_idx(Constant::Null);
2906 self.emit_var_load(name);
2907 self.emit(Opcode::Dup);
2908 self.emit(Opcode::LoadConst);
2909 self.chunk.write_u16(null_c);
2910 self.emit_u8(Opcode::BinOp, binop_to_u8(BinOp::StrictEq));
2911 let j_not_null = self.emit_jump(Opcode::JumpIfFalse);
2912 self.emit(Opcode::Pop);
2913 self.compile_expr(value)?;
2914 self.emit_var_store(name);
2915 self.emit_var_load(name);
2916 let j_end = self.emit_jump(Opcode::Jump);
2917 let end = self.chunk.code.len();
2918 self.patch_jump(j_not_null, end);
2919 self.patch_jump(j_end, end);
2920 }
2921 }
2922 }
2923 Expr::New { callee, args, .. } => {
2924 let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
2925 if has_spread {
2926 self.emit_u16(Opcode::NewArray, 0);
2927 for arg in args {
2928 match arg {
2929 CallArg::Expr(e) => {
2930 self.compile_expr(e)?;
2931 self.emit_u16(Opcode::NewArray, 1);
2932 self.emit(Opcode::ConcatArray);
2933 }
2934 CallArg::Spread(expr) => {
2935 self.compile_expr(expr)?;
2936 self.emit(Opcode::ConcatArray);
2937 }
2938 }
2939 }
2940 self.compile_expr(callee)?;
2941 self.emit(Opcode::ConstructSpread);
2942 } else {
2943 self.compile_expr(callee)?;
2944 for arg in args {
2945 if let CallArg::Expr(e) = arg {
2946 self.compile_expr(e)?;
2947 }
2948 }
2949 self.emit_u16(Opcode::Construct, args.len() as u16);
2950 }
2951 }
2952 }
2953 Ok(())
2954 }
2955
2956 fn compile_jsx_element(
2957 &mut self,
2958 tag: &Arc<str>,
2959 props: &[JsxProp],
2960 children: &[JsxChild],
2961 ) -> Result<(), CompileError> {
2962 let h_idx = self.name_idx(&Arc::from("h"));
2963 self.emit_u16(Opcode::LoadGlobal, h_idx);
2964 let tag_str = tag.as_ref();
2965 let is_component = tag_str
2966 .chars()
2967 .next()
2968 .map(|c| c.is_uppercase())
2969 .unwrap_or(false);
2970 if is_component {
2971 let tag_idx = self.name_idx(tag);
2972 self.emit_u16(Opcode::LoadGlobal, tag_idx);
2973 } else {
2974 let tag_const = self.constant_idx(Constant::String(Arc::from(tag_str)));
2975 self.emit(Opcode::LoadConst);
2976 self.chunk.write_u16(tag_const);
2977 }
2978 self.compile_jsx_props(props)?;
2979 self.compile_jsx_children(children)?;
2980 self.emit_u16(Opcode::Call, 3);
2981 Ok(())
2982 }
2983
2984 fn compile_jsx_fragment(&mut self, children: &[JsxChild]) -> Result<(), CompileError> {
2985 let h_idx = self.name_idx(&Arc::from("h"));
2986 self.emit_u16(Opcode::LoadGlobal, h_idx);
2987 let fragment_idx = self.name_idx(&Arc::from("Fragment"));
2988 self.emit_u16(Opcode::LoadGlobal, fragment_idx);
2989 let null_idx = self.constant_idx(Constant::Null);
2990 self.emit(Opcode::LoadConst);
2991 self.chunk.write_u16(null_idx);
2992 self.compile_jsx_children(children)?;
2993 self.emit_u16(Opcode::Call, 3);
2994 Ok(())
2995 }
2996
2997 fn compile_jsx_props(&mut self, props: &[JsxProp]) -> Result<(), CompileError> {
2998 if props.is_empty() {
2999 let null_idx = self.constant_idx(Constant::Null);
3000 self.emit(Opcode::LoadConst);
3001 self.chunk.write_u16(null_idx);
3002 return Ok(());
3003 }
3004 let has_spread = props.iter().any(|p| matches!(p, JsxProp::Spread(_)));
3005 if has_spread {
3006 self.emit_u16(Opcode::NewObject, 0);
3007 for prop in props {
3008 match prop {
3009 JsxProp::Attr { name, value } => {
3010 let key_idx = self.constant_idx(Constant::String(Arc::clone(name)));
3011 self.emit(Opcode::LoadConst);
3012 self.chunk.write_u16(key_idx);
3013 match value {
3014 JsxAttrValue::String(s) => {
3015 let val_idx = self.constant_idx(Constant::String(Arc::clone(s)));
3016 self.emit(Opcode::LoadConst);
3017 self.chunk.write_u16(val_idx);
3018 }
3019 JsxAttrValue::Expr(e) => self.compile_expr(e)?,
3020 JsxAttrValue::ImplicitTrue => {
3021 let true_idx = self.constant_idx(Constant::Bool(true));
3022 self.emit(Opcode::LoadConst);
3023 self.chunk.write_u16(true_idx);
3024 }
3025 }
3026 self.emit_u16(Opcode::NewObject, 1);
3027 self.emit(Opcode::MergeObject);
3028 }
3029 JsxProp::Spread(expr) => {
3030 self.compile_expr(expr)?;
3031 self.emit(Opcode::MergeObject);
3032 }
3033 }
3034 }
3035 } else {
3036 for prop in props {
3037 if let JsxProp::Attr { name, value } = prop {
3038 let key_idx = self.constant_idx(Constant::String(Arc::clone(name)));
3039 self.emit(Opcode::LoadConst);
3040 self.chunk.write_u16(key_idx);
3041 match value {
3042 JsxAttrValue::String(s) => {
3043 let val_idx = self.constant_idx(Constant::String(Arc::clone(s)));
3044 self.emit(Opcode::LoadConst);
3045 self.chunk.write_u16(val_idx);
3046 }
3047 JsxAttrValue::Expr(e) => self.compile_expr(e)?,
3048 JsxAttrValue::ImplicitTrue => {
3049 let true_idx = self.constant_idx(Constant::Bool(true));
3050 self.emit(Opcode::LoadConst);
3051 self.chunk.write_u16(true_idx);
3052 }
3053 }
3054 }
3055 }
3056 self.emit_u16(Opcode::NewObject, props.len() as u16);
3057 }
3058 Ok(())
3059 }
3060
3061 fn compile_jsx_children(&mut self, children: &[JsxChild]) -> Result<(), CompileError> {
3062 for child in children {
3063 match child {
3064 JsxChild::Text(s) => {
3065 let idx = self.constant_idx(Constant::String(Arc::clone(s)));
3066 self.emit(Opcode::LoadConst);
3067 self.chunk.write_u16(idx);
3068 }
3069 JsxChild::Expr(e) => self.compile_expr(e)?,
3070 }
3071 }
3072 self.emit_u16(Opcode::NewArray, children.len() as u16);
3073 Ok(())
3074 }
3075}
3076
3077pub fn compile(program: &Program) -> Result<Chunk, CompileError> {
3079 compile_internal(program, true, false, None)
3080}
3081
3082pub fn compile_with_source(
3086 program: &Program,
3087 source: Option<std::sync::Arc<str>>,
3088) -> Result<Chunk, CompileError> {
3089 compile_internal(program, true, false, source)
3090}
3091
3092pub fn compile_unoptimized(program: &Program) -> Result<Chunk, CompileError> {
3094 compile_internal(program, false, false, None)
3095}
3096
3097pub fn compile_for_repl(program: &Program) -> Result<Chunk, CompileError> {
3099 compile_internal(program, true, true, None)
3100}
3101
3102pub fn compile_for_repl_unoptimized(program: &Program) -> Result<Chunk, CompileError> {
3104 compile_internal(program, false, true, None)
3105}
3106
3107fn compile_internal(
3108 program: &Program,
3109 peephole: bool,
3110 retain_last_expr: bool,
3111 source: Option<std::sync::Arc<str>>,
3112) -> Result<Chunk, CompileError> {
3113 let mut chunk = Chunk::new();
3114 chunk.source = source; let mut compiler = Compiler::new(&mut chunk, retain_last_expr);
3116 compiler.math_is_global = !program.statements.iter().any(|s| stmt_rebinds(s, "Math"));
3120 compiler.stable_globals = Arc::new(compute_stable_globals(program));
3123 compiler.compile_program(program)?;
3124 if peephole {
3125 crate::peephole::optimize(&mut chunk);
3126 }
3127 Ok(chunk)
3128}
3129
3130#[cfg(test)]
3131mod stable_globals_tests {
3132 use super::compute_stable_globals;
3133
3134 fn stable(src: &str) -> std::collections::HashSet<String> {
3135 let prog = tishlang_parser::parse(src).expect("parse");
3136 compute_stable_globals(&prog)
3137 .into_iter()
3138 .map(|n| n.to_string())
3139 .collect()
3140 }
3141
3142 #[test]
3147 fn all_unreassigned_top_level_fns_are_stable() {
3148 let s = stable(
3149 "function evalA(i, j) { return 1.0 / (i + j) }\n\
3150 function multiplyAv(n, v, av) { let i = 0; while (i < n) { av[i] = evalA(i, i) * v[i]; i = i + 1 } }\n\
3151 function spectralNorm(n) { multiplyAv(n, n, n); return n }\n",
3152 );
3153 assert!(
3154 s.contains("evalA"),
3155 "evalA (called by multiplyAv) must be stable"
3156 );
3157 assert!(s.contains("multiplyAv"));
3158 assert!(s.contains("spectralNorm"));
3159 }
3160
3161 #[test]
3164 fn reassigned_function_is_excluded() {
3165 let s = stable(
3166 "function f(x) { return x + 1 }\n\
3167 function g(x) { return f(x) * 2 }\n\
3168 f = (x) => x + 100\n",
3169 );
3170 assert!(!s.contains("f"), "f is reassigned → must NOT be stable");
3171 assert!(s.contains("g"), "g is never reassigned → stable");
3172 }
3173
3174 #[test]
3177 fn cross_body_rebind_and_shadow_and_redecl_excluded() {
3178 assert!(!stable(
3180 "function h(x) { return x }\n\
3181 function k() { h = 5; return h }\n"
3182 )
3183 .contains("h"));
3184 assert!(!stable(
3186 "function p(x) { return x }\n\
3187 function q(p) { return p }\n"
3188 )
3189 .contains("p"));
3190 assert!(!stable("function d(x) { return x }\nfunction d(y) { return y }\n").contains("d"));
3192 assert!(!stable("function e(x) { return x }\nlet e = 3\n").contains("e"));
3194 }
3195
3196 #[test]
3199 fn param_default_rebind_excluded() {
3200 assert!(!stable(
3202 "function evil(x) { return x + 1000 }\n\
3203 function foo(x) { return x + 1 }\n\
3204 function resetFoo(y = (foo = evil)) { return y }\n"
3205 )
3206 .contains("foo"));
3207 assert!(!stable(
3209 "function bar(x) { return x }\n\
3210 let f = (z = (bar = 5)) => z\n"
3211 )
3212 .contains("bar"));
3213 assert!(
3215 stable("function ok(x) { return x }\nfunction u(a = 1) { return a }\n").contains("ok")
3216 );
3217 }
3218}