@tishlang/tish 1.4.2 → 1.5.0
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.
- package/bin/tish +0 -0
- package/crates/tish/Cargo.toml +2 -2
- package/crates/tish/src/cli_help.rs +504 -0
- package/crates/tish/src/main.rs +76 -90
- package/crates/tish/src/repl_completion.rs +1 -1
- package/crates/tish/tests/integration_test.rs +48 -0
- package/crates/tish_build_utils/src/lib.rs +171 -1
- package/crates/tish_builtins/src/string.rs +248 -0
- package/crates/tish_bytecode/Cargo.toml +1 -0
- package/crates/tish_bytecode/src/compiler.rs +289 -66
- package/crates/tish_bytecode/src/opcode.rs +13 -3
- package/crates/tish_bytecode/src/peephole.rs +21 -16
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_compile/src/codegen.rs +214 -79
- package/crates/tish_compile/src/lib.rs +1 -1
- package/crates/tish_core/src/value.rs +1 -0
- package/crates/tish_eval/src/eval.rs +39 -1
- package/crates/tish_eval/src/lib.rs +1 -1
- package/crates/tish_native/src/build.rs +48 -7
- package/crates/tish_native/src/lib.rs +8 -3
- package/crates/tish_runtime/src/lib.rs +4 -0
- package/crates/tish_vm/src/lib.rs +1 -1
- package/crates/tish_vm/src/vm.rs +155 -16
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -5,8 +5,8 @@ use std::sync::Arc;
|
|
|
5
5
|
|
|
6
6
|
use tishlang_ast::{
|
|
7
7
|
ArrayElement, ArrowBody, BinOp, CallArg, DestructElement, DestructPattern, Expr,
|
|
8
|
-
FunParam, JsxAttrValue, JsxChild, JsxProp, Literal,
|
|
9
|
-
Statement,
|
|
8
|
+
ExportDeclaration, FunParam, JsxAttrValue, JsxChild, JsxProp, Literal, LogicalAssignOp,
|
|
9
|
+
MemberProp, ObjectProp, Program, Span, Statement,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
use crate::chunk::{Chunk, Constant};
|
|
@@ -47,7 +47,13 @@ impl std::error::Error for CompileError {}
|
|
|
47
47
|
/// Loop boundary for break/continue.
|
|
48
48
|
struct LoopInfo {
|
|
49
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).
|
|
50
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,
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/// Switch boundary: break exits the switch.
|
|
@@ -55,6 +61,15 @@ struct SwitchInfo {
|
|
|
55
61
|
break_patches: Vec<usize>,
|
|
56
62
|
}
|
|
57
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
|
+
|
|
58
73
|
struct Compiler<'a> {
|
|
59
74
|
chunk: &'a mut Chunk,
|
|
60
75
|
/// Current scope: variable name -> (depth, is_captured). Depth 0 = local.
|
|
@@ -62,6 +77,10 @@ struct Compiler<'a> {
|
|
|
62
77
|
/// Stack of loop info for break/continue.
|
|
63
78
|
loop_stack: Vec<LoopInfo>,
|
|
64
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,
|
|
65
84
|
/// When true (REPL mode), last ExprStmt leaves its value on the stack and we skip trailing LoadConst Null.
|
|
66
85
|
retain_last_expr: bool,
|
|
67
86
|
}
|
|
@@ -73,10 +92,101 @@ impl<'a> Compiler<'a> {
|
|
|
73
92
|
scope: vec![HashMap::new()],
|
|
74
93
|
loop_stack: Vec::new(),
|
|
75
94
|
switch_stack: Vec::new(),
|
|
95
|
+
breakable_stack: Vec::new(),
|
|
96
|
+
block_depth: 0,
|
|
76
97
|
retain_last_expr,
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
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,
|
|
124
|
+
default,
|
|
125
|
+
..
|
|
126
|
+
} => {
|
|
127
|
+
if default.is_some() {
|
|
128
|
+
return Err(CompileError {
|
|
129
|
+
message: "Default values on destructuring parameters are not supported in bytecode"
|
|
130
|
+
.to_string(),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
names.push(Arc::from(format!("__param_{}", syn_counter)));
|
|
134
|
+
syn_counter += 1;
|
|
135
|
+
slots.push(Some(pattern.clone()));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
Ok((names, slots))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// After VM binds positional args to `param_names`, load each destructure slot and bind pattern locals.
|
|
143
|
+
fn emit_param_destructure_prologue(
|
|
144
|
+
&mut self,
|
|
145
|
+
param_names: &[Arc<str>],
|
|
146
|
+
slots: &[Option<DestructPattern>],
|
|
147
|
+
) -> Result<(), CompileError> {
|
|
148
|
+
debug_assert_eq!(param_names.len(), slots.len());
|
|
149
|
+
for (name, slot) in param_names.iter().zip(slots.iter()) {
|
|
150
|
+
if let Some(pattern) = slot {
|
|
151
|
+
let idx = self.name_idx(name);
|
|
152
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
153
|
+
self.compile_destructure(pattern, false, false)?;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
Ok(())
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fn compile_for_init_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
|
|
160
|
+
match stmt {
|
|
161
|
+
Statement::VarDecl {
|
|
162
|
+
name,
|
|
163
|
+
init,
|
|
164
|
+
mutable: _,
|
|
165
|
+
..
|
|
166
|
+
} => {
|
|
167
|
+
if let Some(expr) = init {
|
|
168
|
+
self.compile_expr(expr)?;
|
|
169
|
+
} else {
|
|
170
|
+
let idx = self.constant_idx(Constant::Null);
|
|
171
|
+
self.emit(Opcode::LoadConst);
|
|
172
|
+
self.chunk.write_u16(idx);
|
|
173
|
+
}
|
|
174
|
+
let idx = self.name_idx(name);
|
|
175
|
+
self.emit_u16(Opcode::DeclareVarPlain, idx);
|
|
176
|
+
self.scope
|
|
177
|
+
.last_mut()
|
|
178
|
+
.unwrap()
|
|
179
|
+
.insert(Arc::clone(name), false);
|
|
180
|
+
}
|
|
181
|
+
Statement::VarDeclDestructure { pattern, init, .. } => {
|
|
182
|
+
self.compile_expr(init)?;
|
|
183
|
+
self.compile_destructure(pattern, false, true)?;
|
|
184
|
+
}
|
|
185
|
+
_ => self.compile_statement(stmt)?,
|
|
186
|
+
}
|
|
187
|
+
Ok(())
|
|
188
|
+
}
|
|
189
|
+
|
|
80
190
|
fn name_idx(&mut self, name: &Arc<str>) -> u16 {
|
|
81
191
|
self.chunk.add_name(Arc::clone(name))
|
|
82
192
|
}
|
|
@@ -122,9 +232,10 @@ impl<'a> Compiler<'a> {
|
|
|
122
232
|
self.chunk.code[patch_pos + 1] = bytes[1];
|
|
123
233
|
}
|
|
124
234
|
|
|
125
|
-
/// Patch a JumpBack operand: distance from
|
|
235
|
+
/// Patch a JumpBack operand: distance from the IP after this insn back to `target`.
|
|
236
|
+
/// `patch_pos` is the first byte of the u16 operand (same as [`Self::emit_jump_back`]'s return value).
|
|
126
237
|
fn patch_jump_back(&mut self, patch_pos: usize, target: usize) {
|
|
127
|
-
let after_insn = patch_pos +
|
|
238
|
+
let after_insn = patch_pos + 2;
|
|
128
239
|
let dist = after_insn.saturating_sub(target);
|
|
129
240
|
let bytes = (dist as u16).to_be_bytes();
|
|
130
241
|
self.chunk.code[patch_pos] = bytes[0];
|
|
@@ -362,11 +473,15 @@ impl<'a> Compiler<'a> {
|
|
|
362
473
|
fn compile_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
|
|
363
474
|
match stmt {
|
|
364
475
|
Statement::Block { statements, .. } => {
|
|
476
|
+
self.emit(Opcode::EnterBlock);
|
|
477
|
+
self.block_depth += 1;
|
|
365
478
|
self.scope.push(HashMap::new());
|
|
366
479
|
for s in statements {
|
|
367
480
|
self.compile_statement(s)?;
|
|
368
481
|
}
|
|
369
482
|
self.scope.pop();
|
|
483
|
+
self.emit(Opcode::ExitBlock);
|
|
484
|
+
self.block_depth -= 1;
|
|
370
485
|
}
|
|
371
486
|
Statement::VarDecl {
|
|
372
487
|
name,
|
|
@@ -382,7 +497,7 @@ impl<'a> Compiler<'a> {
|
|
|
382
497
|
self.chunk.write_u16(idx);
|
|
383
498
|
}
|
|
384
499
|
let idx = self.name_idx(name);
|
|
385
|
-
self.emit_u16(Opcode::
|
|
500
|
+
self.emit_u16(Opcode::DeclareVar, idx);
|
|
386
501
|
self.scope
|
|
387
502
|
.last_mut()
|
|
388
503
|
.unwrap()
|
|
@@ -390,7 +505,7 @@ impl<'a> Compiler<'a> {
|
|
|
390
505
|
}
|
|
391
506
|
Statement::VarDeclDestructure { pattern, init, .. } => {
|
|
392
507
|
self.compile_expr(init)?;
|
|
393
|
-
self.compile_destructure(pattern, false)?;
|
|
508
|
+
self.compile_destructure(pattern, false, false)?;
|
|
394
509
|
}
|
|
395
510
|
Statement::ExprStmt { expr, .. } => {
|
|
396
511
|
self.compile_expr(expr)?;
|
|
@@ -417,7 +532,12 @@ impl<'a> Compiler<'a> {
|
|
|
417
532
|
self.loop_stack.push(LoopInfo {
|
|
418
533
|
break_patches: Vec::new(),
|
|
419
534
|
continue_patches: Vec::new(),
|
|
535
|
+
continue_is_forward_jump: false,
|
|
420
536
|
});
|
|
537
|
+
self.breakable_stack
|
|
538
|
+
.push(Breakable::Loop {
|
|
539
|
+
unwind_depth: self.block_depth,
|
|
540
|
+
});
|
|
421
541
|
self.compile_expr(cond)?;
|
|
422
542
|
let jump_out = self.emit_jump(Opcode::JumpIfFalse);
|
|
423
543
|
// JumpIfFalse already pops condition when taking body
|
|
@@ -427,6 +547,7 @@ impl<'a> Compiler<'a> {
|
|
|
427
547
|
let end = self.chunk.code.len();
|
|
428
548
|
self.patch_jump(jump_out, end);
|
|
429
549
|
let info = self.loop_stack.pop().unwrap();
|
|
550
|
+
self.breakable_stack.pop();
|
|
430
551
|
for p in info.continue_patches {
|
|
431
552
|
self.patch_jump_back(p, start);
|
|
432
553
|
}
|
|
@@ -443,7 +564,7 @@ impl<'a> Compiler<'a> {
|
|
|
443
564
|
} => {
|
|
444
565
|
self.scope.push(HashMap::new());
|
|
445
566
|
if let Some(i) = init {
|
|
446
|
-
self.
|
|
567
|
+
self.compile_for_init_statement(i.as_ref())?;
|
|
447
568
|
}
|
|
448
569
|
let cond_start = self.chunk.code.len();
|
|
449
570
|
if let Some(c) = cond {
|
|
@@ -457,7 +578,12 @@ impl<'a> Compiler<'a> {
|
|
|
457
578
|
self.loop_stack.push(LoopInfo {
|
|
458
579
|
break_patches: Vec::new(),
|
|
459
580
|
continue_patches: Vec::new(),
|
|
581
|
+
continue_is_forward_jump: true,
|
|
460
582
|
});
|
|
583
|
+
self.breakable_stack
|
|
584
|
+
.push(Breakable::Loop {
|
|
585
|
+
unwind_depth: self.block_depth,
|
|
586
|
+
});
|
|
461
587
|
self.compile_statement(body)?;
|
|
462
588
|
let update_start = self.chunk.code.len();
|
|
463
589
|
if let Some(u) = update {
|
|
@@ -466,7 +592,7 @@ impl<'a> Compiler<'a> {
|
|
|
466
592
|
}
|
|
467
593
|
let info = self.loop_stack.pop().unwrap();
|
|
468
594
|
for p in info.continue_patches {
|
|
469
|
-
self.
|
|
595
|
+
self.patch_jump(p, update_start);
|
|
470
596
|
}
|
|
471
597
|
let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(cond_start);
|
|
472
598
|
self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
|
|
@@ -475,6 +601,7 @@ impl<'a> Compiler<'a> {
|
|
|
475
601
|
for p in info.break_patches {
|
|
476
602
|
self.patch_jump(p, end);
|
|
477
603
|
}
|
|
604
|
+
self.breakable_stack.pop();
|
|
478
605
|
self.scope.pop();
|
|
479
606
|
}
|
|
480
607
|
Statement::ForOf { name, iterable, body, .. } => {
|
|
@@ -487,27 +614,32 @@ impl<'a> Compiler<'a> {
|
|
|
487
614
|
let i_idx = self.name_idx(&i_name);
|
|
488
615
|
let len_idx = self.name_idx(&len_name);
|
|
489
616
|
let name_idx = self.name_idx(name);
|
|
490
|
-
self.emit_u16(Opcode::
|
|
617
|
+
self.emit_u16(Opcode::DeclareVar, arr_idx);
|
|
491
618
|
self.scope.last_mut().unwrap().insert(arr_name.clone(), false);
|
|
492
619
|
self.emit_u16(Opcode::LoadVar, arr_idx);
|
|
493
620
|
let len_name_idx = self.name_idx(&Arc::from("length"));
|
|
494
621
|
self.emit_u16(Opcode::GetMember, len_name_idx);
|
|
495
|
-
self.emit_u16(Opcode::
|
|
622
|
+
self.emit_u16(Opcode::DeclareVar, len_idx);
|
|
496
623
|
self.scope.last_mut().unwrap().insert(len_name.clone(), false);
|
|
497
624
|
let zero_idx = self.constant_idx(Constant::Number(0.0));
|
|
498
625
|
self.emit(Opcode::LoadConst);
|
|
499
626
|
self.chunk.write_u16(zero_idx);
|
|
500
|
-
self.emit_u16(Opcode::
|
|
627
|
+
self.emit_u16(Opcode::DeclareVar, i_idx);
|
|
501
628
|
self.scope.last_mut().unwrap().insert(i_name.clone(), false);
|
|
502
629
|
let loop_start = self.chunk.code.len();
|
|
503
630
|
self.loop_stack.push(LoopInfo {
|
|
504
631
|
break_patches: Vec::new(),
|
|
505
632
|
continue_patches: Vec::new(),
|
|
633
|
+
continue_is_forward_jump: false,
|
|
506
634
|
});
|
|
635
|
+
self.breakable_stack
|
|
636
|
+
.push(Breakable::Loop {
|
|
637
|
+
unwind_depth: self.block_depth,
|
|
638
|
+
});
|
|
507
639
|
self.emit_u16(Opcode::LoadVar, arr_idx);
|
|
508
640
|
self.emit_u16(Opcode::LoadVar, i_idx);
|
|
509
641
|
self.emit(Opcode::GetIndex);
|
|
510
|
-
self.emit_u16(Opcode::
|
|
642
|
+
self.emit_u16(Opcode::DeclareVar, name_idx);
|
|
511
643
|
self.scope.last_mut().unwrap().insert(Arc::clone(name), false);
|
|
512
644
|
self.compile_statement(body)?;
|
|
513
645
|
self.emit_u16(Opcode::LoadVar, i_idx);
|
|
@@ -525,6 +657,7 @@ impl<'a> Compiler<'a> {
|
|
|
525
657
|
let end = self.chunk.code.len();
|
|
526
658
|
self.patch_jump(jump_out, end);
|
|
527
659
|
let info = self.loop_stack.pop().unwrap();
|
|
660
|
+
self.breakable_stack.pop();
|
|
528
661
|
for p in info.continue_patches {
|
|
529
662
|
self.patch_jump_back(p, loop_start);
|
|
530
663
|
}
|
|
@@ -544,22 +677,56 @@ impl<'a> Compiler<'a> {
|
|
|
544
677
|
self.emit(Opcode::Return);
|
|
545
678
|
}
|
|
546
679
|
Statement::Break { .. } => {
|
|
680
|
+
let unwind_depth = match self.breakable_stack.last() {
|
|
681
|
+
Some(Breakable::Loop { unwind_depth }) | Some(Breakable::Switch { unwind_depth }) => {
|
|
682
|
+
*unwind_depth
|
|
683
|
+
}
|
|
684
|
+
None => {
|
|
685
|
+
return Err(CompileError {
|
|
686
|
+
message: "break not inside a loop or switch".to_string(),
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
self.emit_exit_blocks_until_depth(unwind_depth);
|
|
547
691
|
let pos = self.emit_jump(Opcode::Jump);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
692
|
+
match self.breakable_stack.last() {
|
|
693
|
+
Some(Breakable::Loop { .. }) => {
|
|
694
|
+
self.loop_stack.last_mut().unwrap().break_patches.push(pos);
|
|
695
|
+
}
|
|
696
|
+
Some(Breakable::Switch { .. }) => {
|
|
697
|
+
self.switch_stack.last_mut().unwrap().break_patches.push(pos);
|
|
698
|
+
}
|
|
699
|
+
None => {}
|
|
556
700
|
}
|
|
557
701
|
}
|
|
558
702
|
Statement::Continue { .. } => {
|
|
559
|
-
let
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
703
|
+
let unwind_depth = self
|
|
704
|
+
.breakable_stack
|
|
705
|
+
.iter()
|
|
706
|
+
.rev()
|
|
707
|
+
.find_map(|b| match b {
|
|
708
|
+
Breakable::Loop { unwind_depth } => Some(*unwind_depth),
|
|
709
|
+
Breakable::Switch { .. } => None,
|
|
710
|
+
})
|
|
711
|
+
.ok_or_else(|| CompileError {
|
|
712
|
+
message: "continue not inside a loop".to_string(),
|
|
713
|
+
})?;
|
|
714
|
+
self.emit_exit_blocks_until_depth(unwind_depth);
|
|
715
|
+
let forward = self
|
|
716
|
+
.loop_stack
|
|
717
|
+
.last()
|
|
718
|
+
.expect("continue not inside a loop")
|
|
719
|
+
.continue_is_forward_jump;
|
|
720
|
+
let pos = if forward {
|
|
721
|
+
self.emit_jump(Opcode::Jump)
|
|
722
|
+
} else {
|
|
723
|
+
self.emit_jump_back()
|
|
724
|
+
};
|
|
725
|
+
self.loop_stack
|
|
726
|
+
.last_mut()
|
|
727
|
+
.expect("continue not inside a loop")
|
|
728
|
+
.continue_patches
|
|
729
|
+
.push(pos);
|
|
563
730
|
}
|
|
564
731
|
Statement::FunDecl {
|
|
565
732
|
name,
|
|
@@ -569,24 +736,11 @@ impl<'a> Compiler<'a> {
|
|
|
569
736
|
async_: _,
|
|
570
737
|
..
|
|
571
738
|
} => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return Err(CompileError {
|
|
575
|
-
message: "Destructuring parameters are not supported in bytecode"
|
|
576
|
-
.to_string(),
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
}
|
|
739
|
+
let formal_len = params.len();
|
|
740
|
+
let (mut param_names, slots) = Self::plan_function_params(params)?;
|
|
580
741
|
let mut inner = Chunk::new();
|
|
581
|
-
let mut param_names: Vec<Arc<str>> = params
|
|
582
|
-
.iter()
|
|
583
|
-
.map(|p| match p {
|
|
584
|
-
FunParam::Simple(tp) => Arc::clone(&tp.name),
|
|
585
|
-
_ => unreachable!(),
|
|
586
|
-
})
|
|
587
|
-
.collect();
|
|
588
742
|
if let Some(rp) = rest_param {
|
|
589
|
-
param_names.push(rp.name
|
|
743
|
+
param_names.push(Arc::clone(&rp.name));
|
|
590
744
|
inner.rest_param_index = (param_names.len() as u16).saturating_sub(1);
|
|
591
745
|
}
|
|
592
746
|
for p in ¶m_names {
|
|
@@ -598,6 +752,7 @@ impl<'a> Compiler<'a> {
|
|
|
598
752
|
.iter()
|
|
599
753
|
.map(|n| (Arc::clone(n), false))
|
|
600
754
|
.collect::<HashMap<_, _>>()];
|
|
755
|
+
inner_comp.emit_param_destructure_prologue(¶m_names[..formal_len], &slots)?;
|
|
601
756
|
inner_comp.compile_statement(body)?;
|
|
602
757
|
inner_comp.emit(Opcode::LoadConst);
|
|
603
758
|
let idx = inner_comp.constant_idx(Constant::Null);
|
|
@@ -608,7 +763,7 @@ impl<'a> Compiler<'a> {
|
|
|
608
763
|
let idx = self.constant_idx(Constant::Closure(nested_idx));
|
|
609
764
|
self.chunk.write_u16(idx);
|
|
610
765
|
let idx = self.name_idx(name);
|
|
611
|
-
self.emit_u16(Opcode::
|
|
766
|
+
self.emit_u16(Opcode::DeclareVar, idx);
|
|
612
767
|
self.scope.last_mut().unwrap().insert(Arc::clone(name), false);
|
|
613
768
|
}
|
|
614
769
|
Statement::DoWhile { body, cond, .. } => {
|
|
@@ -616,7 +771,12 @@ impl<'a> Compiler<'a> {
|
|
|
616
771
|
self.loop_stack.push(LoopInfo {
|
|
617
772
|
break_patches: Vec::new(),
|
|
618
773
|
continue_patches: Vec::new(),
|
|
774
|
+
continue_is_forward_jump: false,
|
|
619
775
|
});
|
|
776
|
+
self.breakable_stack
|
|
777
|
+
.push(Breakable::Loop {
|
|
778
|
+
unwind_depth: self.block_depth,
|
|
779
|
+
});
|
|
620
780
|
self.compile_statement(body)?;
|
|
621
781
|
let cond_start = self.chunk.code.len();
|
|
622
782
|
self.compile_expr(cond)?;
|
|
@@ -626,6 +786,7 @@ impl<'a> Compiler<'a> {
|
|
|
626
786
|
let end = self.chunk.code.len();
|
|
627
787
|
self.patch_jump(jump_back, end);
|
|
628
788
|
let info = self.loop_stack.pop().unwrap();
|
|
789
|
+
self.breakable_stack.pop();
|
|
629
790
|
for p in info.continue_patches {
|
|
630
791
|
self.patch_jump_back(p, cond_start);
|
|
631
792
|
}
|
|
@@ -634,9 +795,13 @@ impl<'a> Compiler<'a> {
|
|
|
634
795
|
}
|
|
635
796
|
}
|
|
636
797
|
Statement::Switch { expr, cases, default_body, .. } => {
|
|
798
|
+
let switch_unwind_depth = self.block_depth;
|
|
637
799
|
self.switch_stack.push(SwitchInfo {
|
|
638
800
|
break_patches: Vec::new(),
|
|
639
801
|
});
|
|
802
|
+
self.breakable_stack.push(Breakable::Switch {
|
|
803
|
+
unwind_depth: switch_unwind_depth,
|
|
804
|
+
});
|
|
640
805
|
self.compile_expr(expr)?;
|
|
641
806
|
self.emit(Opcode::Dup);
|
|
642
807
|
let mut end_patches = Vec::new();
|
|
@@ -684,6 +849,7 @@ impl<'a> Compiler<'a> {
|
|
|
684
849
|
self.patch_jump(p, self.chunk.code.len());
|
|
685
850
|
}
|
|
686
851
|
let sw = self.switch_stack.pop().unwrap();
|
|
852
|
+
self.breakable_stack.pop();
|
|
687
853
|
for p in sw.break_patches {
|
|
688
854
|
self.patch_jump(p, self.chunk.code.len());
|
|
689
855
|
}
|
|
@@ -708,16 +874,23 @@ impl<'a> Compiler<'a> {
|
|
|
708
874
|
let catch_start = self.chunk.code.len();
|
|
709
875
|
if let Some(catch_stmt) = catch_body {
|
|
710
876
|
if let Some(param) = catch_param {
|
|
877
|
+
self.emit(Opcode::EnterBlock);
|
|
878
|
+
self.block_depth += 1;
|
|
879
|
+
self.scope.push(HashMap::new());
|
|
711
880
|
let param_idx = self.name_idx(param);
|
|
712
|
-
self.emit_u16(Opcode::
|
|
881
|
+
self.emit_u16(Opcode::DeclareVar, param_idx);
|
|
713
882
|
self.scope
|
|
714
883
|
.last_mut()
|
|
715
884
|
.unwrap()
|
|
716
885
|
.insert(Arc::clone(param), false);
|
|
886
|
+
self.compile_statement(catch_stmt)?;
|
|
887
|
+
self.scope.pop();
|
|
888
|
+
self.emit(Opcode::ExitBlock);
|
|
889
|
+
self.block_depth -= 1;
|
|
717
890
|
} else {
|
|
718
891
|
self.emit(Opcode::Pop);
|
|
892
|
+
self.compile_statement(catch_stmt)?;
|
|
719
893
|
}
|
|
720
|
-
self.compile_statement(catch_stmt)?;
|
|
721
894
|
} else {
|
|
722
895
|
self.emit(Opcode::Throw);
|
|
723
896
|
}
|
|
@@ -730,11 +903,21 @@ impl<'a> Compiler<'a> {
|
|
|
730
903
|
self.chunk.code[catch_offset_pos + 1] = (catch_offset >> 8) as u8;
|
|
731
904
|
self.chunk.code[catch_offset_pos + 2] = (catch_offset & 0xff) as u8;
|
|
732
905
|
}
|
|
733
|
-
Statement::Import { .. }
|
|
906
|
+
Statement::Import { .. } => {
|
|
734
907
|
return Err(CompileError {
|
|
735
|
-
message: "Import
|
|
908
|
+
message: "Import not supported in bytecode".to_string(),
|
|
736
909
|
});
|
|
737
910
|
}
|
|
911
|
+
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
912
|
+
ExportDeclaration::Named(inner_stmt) => {
|
|
913
|
+
self.compile_statement(inner_stmt.as_ref())?;
|
|
914
|
+
}
|
|
915
|
+
ExportDeclaration::Default(_) => {
|
|
916
|
+
return Err(CompileError {
|
|
917
|
+
message: "export default is not supported in bytecode".to_string(),
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
},
|
|
738
921
|
}
|
|
739
922
|
Ok(())
|
|
740
923
|
}
|
|
@@ -743,7 +926,13 @@ impl<'a> Compiler<'a> {
|
|
|
743
926
|
&mut self,
|
|
744
927
|
pattern: &DestructPattern,
|
|
745
928
|
mutable: bool,
|
|
929
|
+
for_header_binding: bool,
|
|
746
930
|
) -> Result<(), CompileError> {
|
|
931
|
+
let decl_op = if for_header_binding {
|
|
932
|
+
Opcode::DeclareVarPlain
|
|
933
|
+
} else {
|
|
934
|
+
Opcode::DeclareVar
|
|
935
|
+
};
|
|
747
936
|
match pattern {
|
|
748
937
|
DestructPattern::Array(elements) => {
|
|
749
938
|
for (i, elem) in elements.iter().enumerate() {
|
|
@@ -755,7 +944,7 @@ impl<'a> Compiler<'a> {
|
|
|
755
944
|
self.chunk.write_u16(idx);
|
|
756
945
|
self.emit(Opcode::GetIndex);
|
|
757
946
|
let idx = self.name_idx(name);
|
|
758
|
-
self.emit_u16(
|
|
947
|
+
self.emit_u16(decl_op, idx);
|
|
759
948
|
self.scope
|
|
760
949
|
.last_mut()
|
|
761
950
|
.unwrap()
|
|
@@ -780,7 +969,7 @@ impl<'a> Compiler<'a> {
|
|
|
780
969
|
match &prop.value {
|
|
781
970
|
DestructElement::Ident(name) => {
|
|
782
971
|
let idx = self.name_idx(name);
|
|
783
|
-
self.emit_u16(
|
|
972
|
+
self.emit_u16(decl_op, idx);
|
|
784
973
|
if mutable {
|
|
785
974
|
self.scope
|
|
786
975
|
.last_mut()
|
|
@@ -1093,22 +1282,9 @@ impl<'a> Compiler<'a> {
|
|
|
1093
1282
|
self.emit_u16(Opcode::Call, 1);
|
|
1094
1283
|
}
|
|
1095
1284
|
Expr::ArrowFunction { params, body, .. } => {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
return Err(CompileError {
|
|
1099
|
-
message: "Destructuring parameters are not supported in bytecode"
|
|
1100
|
-
.to_string(),
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1285
|
+
let formal_len = params.len();
|
|
1286
|
+
let (param_names, slots) = Self::plan_function_params(params)?;
|
|
1104
1287
|
let mut inner = Chunk::new();
|
|
1105
|
-
let param_names: Vec<Arc<str>> = params
|
|
1106
|
-
.iter()
|
|
1107
|
-
.map(|p| match p {
|
|
1108
|
-
FunParam::Simple(tp) => Arc::clone(&tp.name),
|
|
1109
|
-
_ => unreachable!(),
|
|
1110
|
-
})
|
|
1111
|
-
.collect();
|
|
1112
1288
|
for p in ¶m_names {
|
|
1113
1289
|
inner.add_name(Arc::clone(p));
|
|
1114
1290
|
}
|
|
@@ -1118,6 +1294,7 @@ impl<'a> Compiler<'a> {
|
|
|
1118
1294
|
.iter()
|
|
1119
1295
|
.map(|n| (Arc::clone(n), false))
|
|
1120
1296
|
.collect::<HashMap<_, _>>()];
|
|
1297
|
+
inner_comp.emit_param_destructure_prologue(¶m_names[..formal_len], &slots)?;
|
|
1121
1298
|
match body {
|
|
1122
1299
|
ArrowBody::Expr(e) => {
|
|
1123
1300
|
inner_comp.compile_expr(e)?;
|
|
@@ -1245,10 +1422,56 @@ impl<'a> Compiler<'a> {
|
|
|
1245
1422
|
self.compile_expr(operand)?;
|
|
1246
1423
|
self.emit_u16(Opcode::Call, 1);
|
|
1247
1424
|
}
|
|
1248
|
-
Expr::LogicalAssign { .. } => {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1425
|
+
Expr::LogicalAssign { name, op, value, .. } => {
|
|
1426
|
+
let idx = self.name_idx(name);
|
|
1427
|
+
match op {
|
|
1428
|
+
LogicalAssignOp::OrOr => {
|
|
1429
|
+
// ||= : if current is truthy, keep it; else eval rhs, assign, yield rhs
|
|
1430
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
1431
|
+
self.emit(Opcode::Dup);
|
|
1432
|
+
let j_rhs = self.emit_jump(Opcode::JumpIfFalse);
|
|
1433
|
+
let j_end = self.emit_jump(Opcode::Jump);
|
|
1434
|
+
self.patch_jump(j_rhs, self.chunk.code.len());
|
|
1435
|
+
self.emit(Opcode::Pop);
|
|
1436
|
+
self.compile_expr(value)?;
|
|
1437
|
+
self.emit_u16(Opcode::StoreVar, idx);
|
|
1438
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
1439
|
+
let end = self.chunk.code.len();
|
|
1440
|
+
self.patch_jump(j_end, end);
|
|
1441
|
+
}
|
|
1442
|
+
LogicalAssignOp::AndAnd => {
|
|
1443
|
+
// &&= : if current is falsy, keep it; else eval rhs, assign, yield rhs
|
|
1444
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
1445
|
+
self.emit(Opcode::Dup);
|
|
1446
|
+
let j_short = self.emit_jump(Opcode::JumpIfFalse);
|
|
1447
|
+
self.emit(Opcode::Pop);
|
|
1448
|
+
self.compile_expr(value)?;
|
|
1449
|
+
self.emit_u16(Opcode::StoreVar, idx);
|
|
1450
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
1451
|
+
let j_end = self.emit_jump(Opcode::Jump);
|
|
1452
|
+
let end = self.chunk.code.len();
|
|
1453
|
+
self.patch_jump(j_short, end);
|
|
1454
|
+
self.patch_jump(j_end, end);
|
|
1455
|
+
}
|
|
1456
|
+
LogicalAssignOp::Nullish => {
|
|
1457
|
+
// ??= : assign only when current === null (matches interpreter)
|
|
1458
|
+
let null_c = self.constant_idx(Constant::Null);
|
|
1459
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
1460
|
+
self.emit(Opcode::Dup);
|
|
1461
|
+
self.emit(Opcode::LoadConst);
|
|
1462
|
+
self.chunk.write_u16(null_c);
|
|
1463
|
+
self.emit_u8(Opcode::BinOp, binop_to_u8(BinOp::StrictEq));
|
|
1464
|
+
let j_not_null = self.emit_jump(Opcode::JumpIfFalse);
|
|
1465
|
+
self.emit(Opcode::Pop);
|
|
1466
|
+
self.compile_expr(value)?;
|
|
1467
|
+
self.emit_u16(Opcode::StoreVar, idx);
|
|
1468
|
+
self.emit_u16(Opcode::LoadVar, idx);
|
|
1469
|
+
let j_end = self.emit_jump(Opcode::Jump);
|
|
1470
|
+
let end = self.chunk.code.len();
|
|
1471
|
+
self.patch_jump(j_not_null, end);
|
|
1472
|
+
self.patch_jump(j_end, end);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1252
1475
|
}
|
|
1253
1476
|
Expr::New { callee, args, .. } => {
|
|
1254
1477
|
let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
|
|
@@ -84,13 +84,22 @@ pub enum Opcode {
|
|
|
84
84
|
Construct = 37,
|
|
85
85
|
/// `new callee(...spread)` — stack: args array, then callee (same order as CallSpread).
|
|
86
86
|
ConstructSpread = 38,
|
|
87
|
+
/// Declare `let`/`const` in the current lexical frame (operand: u16 name index). Pops value.
|
|
88
|
+
/// Does not walk enclosing scopes or globals (unlike [`StoreVar`]).
|
|
89
|
+
DeclareVar = 39,
|
|
90
|
+
/// Enter a block scope; pairs with [`ExitBlock`].
|
|
91
|
+
EnterBlock = 40,
|
|
92
|
+
/// Exit innermost block scope and restore shadowed bindings.
|
|
93
|
+
ExitBlock = 41,
|
|
94
|
+
/// Like [`DeclareVar`] but does not record block-scope undo (for `for`/`for-of` header bindings).
|
|
95
|
+
DeclareVarPlain = 42,
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
impl Opcode {
|
|
90
|
-
/// Decode byte to opcode. Safe for b in 0..=
|
|
99
|
+
/// Decode byte to opcode. Safe for b in 0..=42 (matches #[repr(u8)] discriminants).
|
|
91
100
|
#[inline]
|
|
92
101
|
pub fn from_u8(b: u8) -> Option<Opcode> {
|
|
93
|
-
if b <=
|
|
102
|
+
if b <= 42 {
|
|
94
103
|
Some(unsafe { std::mem::transmute(b) })
|
|
95
104
|
} else {
|
|
96
105
|
None
|
|
@@ -101,7 +110,8 @@ impl Opcode {
|
|
|
101
110
|
pub fn instruction_size(self, code: &[u8], ip: usize) -> Option<usize> {
|
|
102
111
|
let size = match self {
|
|
103
112
|
Opcode::Nop | Opcode::Pop | Opcode::Dup | Opcode::Return | Opcode::ExitTry
|
|
104
|
-
| Opcode::ArrayMapIdentity | Opcode::CallSpread | Opcode::ConstructSpread
|
|
113
|
+
| Opcode::ArrayMapIdentity | Opcode::CallSpread | Opcode::ConstructSpread
|
|
114
|
+
| Opcode::EnterBlock | Opcode::ExitBlock => 1,
|
|
105
115
|
Opcode::ArraySortByProperty | Opcode::ArrayMapBinOp | Opcode::ArrayFilterBinOp
|
|
106
116
|
| Opcode::LoadNativeExport => 5,
|
|
107
117
|
_ => 3,
|