[rust] ForStatement support
Implements support for `ForStatement` from swc -> estree -> hir, flushing out more of the HIR representation and porting pieces from HIRBuilder as necessary.
Joe Savona committed
Jul 8, 2023 at 22:59 UTC
8176f25719da6f99be829f4e1400a10f34c6bff9
9 files changed
+393
-76
compiler/forget/crates/build-hir/src/build.rs
+105
-13
@@ -1,14 +1,15 @@
1
use bumpalo::collections::{CollectIn, String};
2
use estree::{
3
- AssignmentTarget, ExpressionLike, FunctionDeclaration, IfStatement, Literal, LiteralValue,
4
- Pattern, Statement, VariableDeclarationKind,
3
+ AssignmentTarget, BinaryExpression, ExpressionLike, ForInit, ForStatement, FunctionDeclaration,
4
+ IfStatement, Literal, LiteralValue, Pattern, Statement, VariableDeclarationKind,
5
};
6
use hir::{
7
- ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionKind,
8
- InstructionValue, LValue, LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue,
7
+ ArrayElement, BlockKind, BranchTerminal, Environment, ForTerminal, Function, GotoKind,
8
+ Identifier, InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal, Place,
9
+ PrimitiveValue, TerminalValue,
10
};
11
11
-use crate::builder::{Binding, Builder};
12
+use crate::builder::{Binding, Builder, LoopScope};
13
14
/// Converts a React function in ESTree format into HIR. Returns the HIR
15
/// if it was constructed sucessfully, otherwise a list of diagnostics
@@ -35,7 +36,7 @@ pub fn build<'a>(
36
}),
37
);
38
builder.terminate(
38
- TerminalValue::ReturnTerminal(hir::ReturnTerminal {
39
+ TerminalValue::Return(hir::ReturnTerminal {
40
value: implicit_return_value,
41
}),
42
hir::BlockKind::Block,
@@ -55,7 +56,7 @@ fn lower_statement<'a>(
56
env: &'a Environment<'a>,
57
builder: &mut Builder<'a>,
58
stmt: Statement,
58
- _label: Option<String<'a>>,
59
+ label: Option<String<'a>>,
60
) -> Result<(), Diagnostic> {
61
match stmt {
62
Statement::BlockStatement(stmt) => {
@@ -66,7 +67,7 @@ fn lower_statement<'a>(
67
Statement::BreakStatement(stmt) => {
68
let block = builder.resolve_break(stmt.label.as_ref())?;
69
builder.terminate(
69
- TerminalValue::GotoTerminal(hir::GotoTerminal {
70
+ TerminalValue::Goto(hir::GotoTerminal {
71
block,
72
kind: GotoKind::Break,
73
}),
@@ -76,7 +77,7 @@ fn lower_statement<'a>(
77
Statement::ContinueStatement(stmt) => {
78
let block = builder.resolve_continue(stmt.label.as_ref())?;
79
builder.terminate(
79
- TerminalValue::GotoTerminal(hir::GotoTerminal {
80
+ TerminalValue::Goto(hir::GotoTerminal {
81
block,
82
kind: GotoKind::Continue,
83
}),
@@ -95,7 +96,7 @@ fn lower_statement<'a>(
96
),
97
};
98
builder.terminate(
98
- TerminalValue::ReturnTerminal(hir::ReturnTerminal { value }),
99
+ TerminalValue::Return(hir::ReturnTerminal { value }),
100
BlockKind::Block,
101
);
102
}
@@ -158,7 +159,7 @@ fn lower_statement<'a>(
159
160
let consequent_block = builder.enter(BlockKind::Block, |builder| {
161
lower_statement(env, builder, consequent, None).unwrap();
161
- TerminalValue::GotoTerminal(hir::GotoTerminal {
162
+ TerminalValue::Goto(hir::GotoTerminal {
163
block: fallthrough_block.id,
164
kind: GotoKind::Break,
165
})
@@ -168,14 +169,14 @@ fn lower_statement<'a>(
169
if let Some(alternate) = alternate {
170
lower_statement(env, builder, alternate, None).unwrap();
171
}
171
- TerminalValue::GotoTerminal(hir::GotoTerminal {
172
+ TerminalValue::Goto(hir::GotoTerminal {
173
block: fallthrough_block.id,
174
kind: GotoKind::Break,
175
})
176
});
177
178
let test = lower_expression_to_temporary(env, builder, test);
178
- let terminal = TerminalValue::IfTerminal(hir::IfTerminal {
179
+ let terminal = TerminalValue::If(hir::IfTerminal {
180
test,
181
consequent: consequent_block,
182
alternate: alternate_block,
@@ -183,6 +184,80 @@ fn lower_statement<'a>(
184
});
185
builder.terminate_with_fallthrough(terminal, fallthrough_block);
186
}
187
+ Statement::ForStatement(stmt) => {
188
+ let ForStatement {
189
+ init,
190
+ test,
191
+ update,
192
+ body,
193
+ ..
194
+ } = *stmt;
195
+
196
+ // Block for the loop's test condition
197
+ let test_block = builder.reserve(BlockKind::Loop);
198
+
199
+ // Block for code following the loop
200
+ let fallthrough_block = builder.reserve(BlockKind::Block);
201
+
202
+ let init_block = builder.enter(BlockKind::Loop, |builder| {
203
+ if let Some(ForInit::VariableDeclaration(decl)) = init {
204
+ lower_statement(env, builder, Statement::VariableDeclaration(decl), None)
205
+ .unwrap();
206
+ TerminalValue::Goto(hir::GotoTerminal {
207
+ block: test_block.id,
208
+ kind: GotoKind::Break,
209
+ })
210
+ } else {
211
+ panic!("Expected for statement to have a variable declaration initializer")
212
+ }
213
+ });
214
+
215
+ let update_block = update.map(|update| {
216
+ builder.enter(BlockKind::Loop, |builder| {
217
+ lower_expression_to_temporary(env, builder, update);
218
+ TerminalValue::Goto(hir::GotoTerminal {
219
+ block: test_block.id,
220
+ kind: GotoKind::Break,
221
+ })
222
+ })
223
+ });
224
+
225
+ let body_block = builder.enter(BlockKind::Block, |builder| {
226
+ let loop_ = LoopScope {
227
+ label,
228
+ continue_block: update_block.unwrap_or(test_block.id),
229
+ break_block: fallthrough_block.id,
230
+ };
231
+ builder.enter_loop(loop_, |builder| {
232
+ lower_statement(env, builder, body, None).unwrap();
233
+ TerminalValue::Goto(hir::GotoTerminal {
234
+ block: update_block.unwrap_or(test_block.id),
235
+ kind: GotoKind::Continue,
236
+ })
237
+ })
238
+ });
239
+
240
+ let terminal = TerminalValue::For(ForTerminal {
241
+ body: body_block,
242
+ init: init_block,
243
+ test: test_block.id,
244
+ fallthrough: fallthrough_block.id,
245
+ update: update_block,
246
+ });
247
+ builder.terminate_with_fallthrough(terminal, test_block);
248
+
249
+ if let Some(test) = test {
250
+ let test_value = lower_expression_to_temporary(env, builder, test);
251
+ let terminal = TerminalValue::Branch(BranchTerminal {
252
+ test: test_value,
253
+ consequent: body_block,
254
+ alternate: fallthrough_block.id,
255
+ });
256
+ builder.terminate_with_fallthrough(terminal, fallthrough_block);
257
+ } else {
258
+ panic!("Expected for statement to have a tesst block");
259
+ }
260
+ }
261
_ => todo!("Lower {stmt:#?}"),
262
}
263
Ok(())
@@ -240,6 +315,7 @@ fn lower_expression<'a>(
315
.collect_in(env.allocator);
316
InstructionValue::Array(hir::Array { elements })
317
}
318
+
319
ExpressionLike::AssignmentExpression(expr) => match expr.operator {
320
estree::AssignmentOperator::Equals => {
321
let right = lower_expression_to_temporary(env, builder, expr.right);
@@ -248,6 +324,22 @@ fn lower_expression<'a>(
324
_ => todo!("lower assignment expr {:#?}", expr),
325
},
326
327
+ ExpressionLike::BinaryExpression(expr) => {
328
+ let BinaryExpression {
329
+ left,
330
+ operator,
331
+ right,
332
+ ..
333
+ } = *expr;
334
+ let left = lower_expression_to_temporary(env, builder, left);
335
+ let right = lower_expression_to_temporary(env, builder, right);
336
+ InstructionValue::Binary(hir::Binary {
337
+ left,
338
+ operator,
339
+ right,
340
+ })
341
+ }
342
+
343
// Cases that cannot appear in expression position but which are included in ExpressionLike
344
// to make serialization easier
345
ExpressionLike::SpreadElement(_) => {
compiler/forget/crates/build-hir/src/builder.rs
+113
-20
@@ -1,4 +1,4 @@
1
-use bumpalo::collections::Vec;
1
+use bumpalo::collections::{String, Vec};
2
use std::{cell::RefCell, collections::HashSet, rc::Rc};
3
4
use hir::{
@@ -27,6 +27,8 @@ pub(crate) struct Builder<'a> {
27
wip: WipBlock<'a>,
28
29
id_gen: InstructionIdGenerator,
30
+
31
+ scopes: Vec<'a, ControlFlowScope<'a>>,
32
}
33
34
pub(crate) struct WipBlock<'a> {
@@ -35,6 +37,48 @@ pub(crate) struct WipBlock<'a> {
37
pub instructions: Vec<'a, Instruction<'a>>,
38
}
39
40
+pub(crate) enum Binding<'a> {
41
+ Local(Identifier<'a>),
42
+ Module(Identifier<'a>),
43
+ Global,
44
+}
45
+
46
+#[derive(Clone, PartialEq, Eq, Debug)]
47
+enum ControlFlowScope<'a> {
48
+ Loop(LoopScope<'a>),
49
+ // Switch(SwitchScope<'a>),
50
+ Label(LabelScope<'a>),
51
+}
52
+
53
+#[derive(Clone, PartialEq, Eq, Debug)]
54
+pub(crate) struct LoopScope<'a> {
55
+ pub label: Option<String<'a>>,
56
+ pub continue_block: BlockId,
57
+ pub break_block: BlockId,
58
+}
59
+
60
+#[derive(Clone, PartialEq, Eq, Debug)]
61
+pub(crate) struct LabelScope<'a> {
62
+ pub label: String<'a>,
63
+ pub block: BlockId,
64
+}
65
+
66
+impl<'a> ControlFlowScope<'a> {
67
+ fn label(&self) -> Option<&String<'a>> {
68
+ match self {
69
+ Self::Loop(scope) => scope.label.as_ref(),
70
+ Self::Label(scope) => Some(&scope.label),
71
+ }
72
+ }
73
+
74
+ fn break_block(&self) -> BlockId {
75
+ match self {
76
+ Self::Loop(scope) => scope.break_block,
77
+ Self::Label(scope) => scope.block,
78
+ }
79
+ }
80
+}
81
+
82
impl<'a> Builder<'a> {
83
pub(crate) fn new(environment: &'a Environment<'a>) -> Self {
84
let entry = environment.next_block_id();
@@ -49,6 +93,7 @@ impl<'a> Builder<'a> {
93
entry,
94
wip: current,
95
id_gen: InstructionIdGenerator::new(),
96
+ scopes: Vec::new_in(&environment.allocator),
97
}
98
}
99
@@ -155,6 +200,17 @@ impl<'a> Builder<'a> {
200
);
201
}
202
203
+ pub(crate) fn enter_loop<F>(&mut self, scope: LoopScope<'a>, f: F) -> TerminalValue<'a>
204
+ where
205
+ F: FnOnce(&mut Self) -> TerminalValue<'a>,
206
+ {
207
+ self.scopes.push(ControlFlowScope::Loop(scope.clone()));
208
+ let terminal = f(self);
209
+ let last = self.scopes.pop().unwrap();
210
+ assert_eq!(last, ControlFlowScope::Loop(scope));
211
+ terminal
212
+ }
213
+
214
/// Returns a new temporary identifier
215
pub(crate) fn make_temporary(&self) -> hir::Identifier<'a> {
216
hir::Identifier {
@@ -173,9 +229,21 @@ impl<'a> Builder<'a> {
229
/// provided but cannot be resolved.
230
pub(crate) fn resolve_break(
231
&self,
176
- _label: Option<&estree::Identifier>,
232
+ label: Option<&estree::Identifier>,
233
) -> Result<BlockId, Diagnostic> {
178
- todo!()
234
+ for scope in self.scopes.iter().rev() {
235
+ match (label, scope.label()) {
236
+ // If this is an unlabeled break, return the most recent break target
237
+ (None, _) => return Ok(scope.break_block()),
238
+ // If the break is labeled and matches the current scope, return its break target
239
+ (Some(label), Some(scope_label)) if &label.name == scope_label => {
240
+ return Ok(scope.break_block());
241
+ }
242
+ // Otherwise keep searching
243
+ _ => continue,
244
+ }
245
+ }
246
+ Err(())
247
}
248
249
/// Resolves the target for the given continue label (if present), or returns the default
@@ -183,9 +251,36 @@ impl<'a> Builder<'a> {
251
/// provided but cannot be resolved.
252
pub(crate) fn resolve_continue(
253
&self,
186
- _label: Option<&estree::Identifier>,
254
+ label: Option<&estree::Identifier>,
255
) -> Result<BlockId, Diagnostic> {
188
- todo!()
256
+ for scope in self.scopes.iter().rev() {
257
+ match scope {
258
+ ControlFlowScope::Loop(scope) => {
259
+ match (label, &scope.label) {
260
+ // If this is an unlabeled continue, return the first matching loop
261
+ (None, _) => return Ok(scope.continue_block),
262
+ // If the continue is labeled and matches the current scope, return its continue target
263
+ (Some(label), Some(scope_label))
264
+ if label.name.as_str() == scope_label.as_str() =>
265
+ {
266
+ return Ok(scope.continue_block);
267
+ }
268
+ // Otherwise keep searching
269
+ _ => continue,
270
+ }
271
+ }
272
+ _ => {
273
+ match (label, scope.label()) {
274
+ (Some(label), Some(scope_label)) if label.name.as_str() == scope_label => {
275
+ // Error, the continue referred to a label that is not a loop
276
+ return Err(());
277
+ }
278
+ _ => continue,
279
+ }
280
+ }
281
+ }
282
+ }
283
+ Err(())
284
}
285
286
pub(crate) fn resolve_binding(
@@ -206,12 +301,6 @@ impl<'a> Builder<'a> {
301
}
302
}
303
209
-pub(crate) enum Binding<'a> {
210
- Local(Identifier<'a>),
211
- Module(Identifier<'a>),
212
- Global,
213
-}
214
-
304
/// Modifies the HIR to put the blocks in reverse postorder, with predecessors before
305
/// successors (except for the case of loops)
306
fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
@@ -230,20 +319,24 @@ fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
319
let block = hir.block(block_id);
320
let terminal = &block.terminal;
321
match &terminal.value {
233
- TerminalValue::IfTerminal(terminal) => {
322
+ TerminalValue::Branch(terminal) => {
323
+ visit(terminal.alternate, hir, visited, postorder);
324
+ visit(terminal.consequent, hir, visited, postorder);
325
+ }
326
+ TerminalValue::If(terminal) => {
327
visit(terminal.alternate, hir, visited, postorder);
328
visit(terminal.consequent, hir, visited, postorder);
329
}
237
- TerminalValue::ForTerminal(terminal) => {
330
+ TerminalValue::For(terminal) => {
331
visit(terminal.init, hir, visited, postorder);
332
}
240
- TerminalValue::DoWhileTerminal(terminal) => {
333
+ TerminalValue::DoWhile(terminal) => {
334
visit(terminal.body, hir, visited, postorder);
335
}
243
- TerminalValue::GotoTerminal(terminal) => {
336
+ TerminalValue::Goto(terminal) => {
337
visit(terminal.block, hir, visited, postorder);
338
}
246
- TerminalValue::ReturnTerminal(..) => { /* no-op */ }
339
+ TerminalValue::Return(..) => { /* no-op */ }
340
}
341
postorder.push(block_id);
342
}
@@ -263,7 +356,7 @@ fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
356
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
357
358
for block in hir.blocks.values_mut() {
266
- if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value {
359
+ if let TerminalValue::For(terminal) = &mut block.terminal.value {
360
if let Some(update) = terminal.update {
361
if !block_ids.contains(&update) {
362
terminal.update = None;
@@ -297,9 +390,9 @@ fn remove_unreachable_do_while_statements<'a>(hir: &mut HIR<'a>) {
390
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
391
392
for block in hir.blocks.values_mut() {
300
- if let TerminalValue::DoWhileTerminal(terminal) = &mut block.terminal.value {
393
+ if let TerminalValue::DoWhile(terminal) = &mut block.terminal.value {
394
if !block_ids.contains(&terminal.test) {
302
- block.terminal.value = TerminalValue::GotoTerminal(hir::GotoTerminal {
395
+ block.terminal.value = TerminalValue::Goto(hir::GotoTerminal {
396
block: terminal.body,
397
kind: GotoKind::Break,
398
});
@@ -353,7 +446,7 @@ fn mark_predecessors<'a>(hir: &mut HIR<'a>) {
446
447
fn invariant<F>(cond: bool, f: F) -> Result<(), Diagnostic>
448
where
356
- F: FnOnce() -> String,
449
+ F: FnOnce() -> std::string::String,
450
{
451
if !cond {
452
let msg = f();
compiler/forget/crates/estree-swc/src/lib.rs
+34
-13
@@ -6,8 +6,8 @@ use swc_core::common::errors::Handler;
6
use swc_core::common::source_map::Pos;
7
use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS};
8
use swc_core::ecma::ast::{
9
- AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, MemberExpr, ModuleItem, Pat,
10
- PatOrExpr, Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
9
+ AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, MemberExpr, MemberProp,
10
+ ModuleItem, Pat, PatOrExpr, Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
11
};
12
use swc_core::ecma::parser::Syntax;
13
use swc_core::ecma::transforms::base::resolver;
@@ -366,6 +366,9 @@ fn convert_expression(cx: &Context, expr: &Expr) -> estree::ExpressionLike {
366
range: convert_span(&expr.span),
367
}))
368
}
369
+ Expr::Member(expr) => {
370
+ estree::ExpressionLike::MemberExpression(Box::new(convert_member_expression(cx, expr)))
371
+ }
372
_ => todo!("translate expression {:#?}", expr),
373
}
374
}
@@ -376,22 +379,40 @@ fn convert_assignment_target(cx: &Context, target: &PatOrExpr) -> estree::Assign
379
estree::AssignmentTarget::Pattern(Box::new(convert_pattern(cx, target)))
380
}
381
PatOrExpr::Expr(target) => {
379
- if let Expr::Member(target) = target.as_ref() {
380
- estree::AssignmentTarget::MemberExpression(Box::new(convert_member_expression(
381
- cx, target,
382
- )))
383
- } else {
384
- panic!(
385
- "Invalid input, expected either a pattern or member expression, got {:#?}",
386
- target
387
- )
382
+ match target.as_ref() {
383
+ Expr::Member(target) => estree::AssignmentTarget::MemberExpression(Box::new(
384
+ convert_member_expression(cx, target),
385
+ )),
386
+ Expr::Ident(target) => estree::AssignmentTarget::Pattern(Box::new(
387
+ estree::Pattern::Identifier(Box::new(convert_identifier(cx, target))),
388
+ )),
389
+ _ => {
390
+ panic!("Expected assignment target to be member expression or identifier, got {:#?}", target)
391
+ }
392
}
393
}
394
}
395
}
396
393
-fn convert_member_expression(_cx: &Context, _expr: &MemberExpr) -> estree::MemberExpression {
394
- todo!("convert member expression")
397
+fn convert_member_expression(cx: &Context, expr: &MemberExpr) -> estree::MemberExpression {
398
+ let (is_computed, property) = match &expr.prop {
399
+ MemberProp::Ident(prop) => (
400
+ false,
401
+ estree::ExpressionLike::Identifier(Box::new(convert_identifier(cx, prop))),
402
+ ),
403
+ MemberProp::Computed(prop) => (true, convert_expression(cx, &prop.expr)),
404
+ _ => {
405
+ panic!("PrivateName member expression properties are not supported")
406
+ }
407
+ };
408
+ estree::MemberExpression {
409
+ object: convert_expression(cx, &expr.obj),
410
+ property,
411
+ is_computed,
412
+ is_optional: false,
413
+ loc: None,
414
+ range: convert_span(&expr.span),
415
+ }
416
}
417
418
fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator {
compiler/forget/crates/estree/src/lib.rs
+12
-1
@@ -1,6 +1,6 @@
1
use serde::{Deserialize, Serialize};
2
use static_assertions::assert_eq_size;
3
-use std::num::NonZeroU32;
3
+use std::{fmt::Display, num::NonZeroU32};
4
5
#[derive(Serialize, Deserialize, Debug)]
6
pub struct SourceLocation {
@@ -689,6 +689,17 @@ pub enum BinaryOperator {
689
Instanceof,
690
}
691
692
+impl Display for BinaryOperator {
693
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
694
+ let name = match self {
695
+ Self::Plus => "+",
696
+ Self::LessThan => "<",
697
+ _ => todo!("display for operator: {:#?}", self),
698
+ };
699
+ f.write_str(name)
700
+ }
701
+}
702
+
703
#[derive(Serialize, Deserialize, Debug)]
704
pub struct CallExpression {
705
#[serde(rename = "optional")]
compiler/forget/crates/fixtures/tests/fixtures/for-statement.js
new
+7
@@ -0,0 +1,7 @@
1
+function foo() {
2
+ let x = 0;
3
+ for (let i = 0; i < 10; i = i + 1) {
4
+ x = x + i;
5
+ }
6
+ return x;
7
+}
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@for-statement.js.snap
new
+46
@@ -0,0 +1,46 @@
1
+---
2
+source: crates/fixtures/tests/fixtures_test.rs
3
+expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
4
+input_file: crates/fixtures/tests/fixtures/for-statement.js
5
+---
6
+Input:
7
+function foo() {
8
+ let x = 0;
9
+ for (let i = 0; i < 10; i = i + 1) {
10
+ x = x + i;
11
+ }
12
+ return x;
13
+}
14
+
15
+
16
+Output:
17
+entry bb0
18
+bb0
19
+ [0] unknown $0 = 0
20
+ [1] unknown $2 = StoreLocal Let unknown x$1 = unknown $0
21
+ [2] For init=bb3 test=bb1 update=bb4 body=bb5 fallthrough=bb2
22
+bb3
23
+ [3] unknown $3 = 0
24
+ [4] unknown $5 = StoreLocal Let unknown i$4 = unknown $3
25
+ [5] Goto bb1
26
+bb1
27
+ [6] unknown $14 = LoadLocal unknown i$4
28
+ [7] unknown $15 = 10
29
+ [8] unknown $16 = Binary unknown $14 < unknown $15
30
+ [9] Branch unknown $16 consequent=bb5 alternate=bb2
31
+bb5
32
+ [10] unknown $10 = LoadLocal unknown x$1
33
+ [11] unknown $11 = LoadLocal unknown i$4
34
+ [12] unknown $12 = Binary unknown $10 + unknown $11
35
+ [13] unknown $13 = StoreLocal Reassign unknown x$1 = unknown $12
36
+ [14] Goto bb4
37
+bb4
38
+ [15] unknown $6 = LoadLocal unknown i$4
39
+ [16] unknown $7 = 1
40
+ [17] unknown $8 = Binary unknown $6 + unknown $7
41
+ [18] unknown $9 = StoreLocal Reassign unknown i$4 = unknown $8
42
+ [19] Goto bb1
43
+bb2
44
+ [20] unknown $17 = LoadLocal unknown x$1
45
+ [21] Return unknown $17
46
+
compiler/forget/crates/hir/src/instruction.rs
+9
-1
@@ -1,6 +1,7 @@
1
use std::{cell::RefCell, fmt::Display, rc::Rc};
2
3
use bumpalo::collections::{String, Vec};
4
+use estree::BinaryOperator;
5
6
use crate::{IdentifierId, InstructionId, ScopeId, Type};
7
@@ -15,7 +16,7 @@ pub struct Instruction<'a> {
16
pub enum InstructionValue<'a> {
17
Array(Array<'a>),
18
// Await(Await<'a>),
18
- // Binary(Binary<'a>),
19
+ Binary(Binary<'a>),
20
// Call(Call<'a>),
21
// ComputedDelete(ComputedDelete<'a>),
22
// ComputedLoad(ComputedLoad<'a>),
@@ -59,6 +60,13 @@ pub enum ArrayElement<'a> {
60
Spread(Place<'a>),
61
}
62
63
+#[derive(Debug)]
64
+pub struct Binary<'a> {
65
+ pub left: Place<'a>,
66
+ pub operator: BinaryOperator,
67
+ pub right: Place<'a>,
68
+}
69
+
70
#[derive(Debug)]
71
pub struct Primitive<'a> {
72
pub value: PrimitiveValue<'a>,
compiler/forget/crates/hir/src/print.rs
+32
-3
@@ -90,6 +90,12 @@ impl<'a> Print for InstructionValue<'a> {
90
write!(out, "DeclareLocal ")?;
91
value.lvalue.print(out)?;
92
}
93
+ InstructionValue::Binary(value) => {
94
+ write!(out, "Binary ")?;
95
+ value.left.print(out)?;
96
+ write!(out, " {} ", value.operator)?;
97
+ value.right.print(out)?;
98
+ }
99
_ => write!(out, "{:?}", self)?,
100
}
101
Ok(())
@@ -146,14 +152,14 @@ impl<'a> Print for Terminal<'a> {
152
impl<'a> Print for TerminalValue<'a> {
153
fn print(&self, out: &mut impl Write) -> Result {
154
match self {
149
- TerminalValue::ReturnTerminal(terminal) => {
155
+ TerminalValue::Return(terminal) => {
156
write!(out, "Return ")?;
157
terminal.value.print(out)?;
158
}
153
- TerminalValue::GotoTerminal(terminal) => {
159
+ TerminalValue::Goto(terminal) => {
160
write!(out, "Goto {}", terminal.block)?;
161
}
156
- TerminalValue::IfTerminal(terminal) => {
162
+ TerminalValue::If(terminal) => {
163
write!(out, "If ")?;
164
terminal.test.print(out)?;
165
write!(
@@ -167,6 +173,29 @@ impl<'a> Print for TerminalValue<'a> {
173
}
174
)?;
175
}
176
+ TerminalValue::Branch(terminal) => {
177
+ write!(out, "Branch ")?;
178
+ terminal.test.print(out)?;
179
+ write!(
180
+ out,
181
+ " consequent={} alternate={}",
182
+ terminal.consequent, terminal.alternate,
183
+ )?;
184
+ }
185
+ TerminalValue::For(terminal) => {
186
+ write!(
187
+ out,
188
+ "For init={} test={} update={} body={} fallthrough={}",
189
+ terminal.init,
190
+ terminal.test,
191
+ match terminal.update {
192
+ Some(fallthrough) => format!("{fallthrough}"),
193
+ None => "<none>".to_string(),
194
+ },
195
+ terminal.body,
196
+ terminal.fallthrough,
197
+ )?;
198
+ }
199
_ => write!(out, "{:?}", self)?,
200
}
201
Ok(())
compiler/forget/crates/hir/src/terminal.rs
+35
-25
@@ -10,22 +10,22 @@ pub struct Terminal<'a> {
10
11
#[derive(Debug)]
12
pub enum TerminalValue<'a> {
13
- // BranchTerminal(BranchTerminal),
14
- DoWhileTerminal(DoWhileTerminal),
15
- // ForOfTerminal(ForOfTerminal),
16
- ForTerminal(ForTerminal),
17
- GotoTerminal(GotoTerminal),
18
- IfTerminal(IfTerminal<'a>),
19
- // LabelTerminal(LabelTerminal),
20
- // LogicalTerminal(LogicalTerminal),
21
- // OptionalTerminal(OptionalTerminal),
22
- ReturnTerminal(ReturnTerminal<'a>),
23
- // SequenceTerminal(SequenceTerminal),
24
- // SwitchTerminal(SwitchTerminal),
25
- // TernaryTerminal(TernaryTerminal),
26
- // ThrowTerminal(ThrowTerminal),
27
- // UnsupportedTerminal(UnsupportedTerminal),
28
- // WhileTerminal(WhileTerminal),
13
+ Branch(BranchTerminal<'a>),
14
+ DoWhile(DoWhileTerminal),
15
+ // ForOf(ForOfTerminal),
16
+ For(ForTerminal),
17
+ Goto(GotoTerminal),
18
+ If(IfTerminal<'a>),
19
+ // Label(LabelTerminal),
20
+ // Logical(LogicalTerminal),
21
+ // Optional(OptionalTerminal),
22
+ Return(ReturnTerminal<'a>),
23
+ // Sequence(SequenceTerminal),
24
+ // Switch(SwitchTerminal),
25
+ // Ternary(TernaryTerminal),
26
+ // Throw(ThrowTerminal),
27
+ // Unsupported(UnsupportedTerminal),
28
+ // While(WhileTerminal),
29
}
30
31
impl<'a> TerminalValue<'a> {
@@ -34,43 +34,53 @@ impl<'a> TerminalValue<'a> {
34
F: Fn(BlockId) -> Option<BlockId>,
35
{
36
match self {
37
- Self::IfTerminal(terminal) => {
37
+ Self::If(terminal) => {
38
terminal.fallthrough = match terminal.fallthrough {
39
Some(fallthrough) => f(fallthrough),
40
_ => None,
41
}
42
}
43
- Self::DoWhileTerminal(DoWhileTerminal { fallthrough, .. })
44
- | Self::ForTerminal(ForTerminal { fallthrough, .. }) => {
43
+ Self::DoWhile(DoWhileTerminal { fallthrough, .. })
44
+ | Self::For(ForTerminal { fallthrough, .. }) => {
45
// statically detect if fallthrough is changed to Option so
46
// that we can update to map the fallthrough w f()
47
let _: BlockId = *fallthrough;
48
}
49
- Self::GotoTerminal(_) | Self::ReturnTerminal(_) => {}
49
+ Self::Branch(_) | Self::Goto(_) | Self::Return(_) => {}
50
}
51
}
52
53
pub fn successors(&self) -> Vec<BlockId> {
54
match self {
55
- Self::IfTerminal(terminal) => {
55
+ Self::If(terminal) => {
56
vec![terminal.consequent, terminal.alternate]
57
}
58
- Self::ForTerminal(terminal) => {
58
+ Self::Branch(terminal) => {
59
+ vec![terminal.consequent, terminal.alternate]
60
+ }
61
+ Self::For(terminal) => {
62
vec![terminal.init]
63
}
61
- Self::DoWhileTerminal(terminal) => {
64
+ Self::DoWhile(terminal) => {
65
vec![terminal.body]
66
}
64
- Self::GotoTerminal(terminal) => {
67
+ Self::Goto(terminal) => {
68
vec![terminal.block]
69
}
67
- Self::ReturnTerminal(_) => {
70
+ Self::Return(_) => {
71
vec![]
72
}
73
}
74
}
75
}
76
77
+#[derive(Debug)]
78
+pub struct BranchTerminal<'a> {
79
+ pub test: Place<'a>,
80
+ pub consequent: BlockId,
81
+ pub alternate: BlockId,
82
+}
83
+
84
#[derive(Debug)]
85
pub struct GotoTerminal {
86
pub block: BlockId,