mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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.
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
use bumpalo::collections::{CollectIn, String};
|
||||
use estree::{
|
||||
AssignmentTarget, ExpressionLike, FunctionDeclaration, IfStatement, Literal, LiteralValue,
|
||||
Pattern, Statement, VariableDeclarationKind,
|
||||
AssignmentTarget, BinaryExpression, ExpressionLike, ForInit, ForStatement, FunctionDeclaration,
|
||||
IfStatement, Literal, LiteralValue, Pattern, Statement, VariableDeclarationKind,
|
||||
};
|
||||
use hir::{
|
||||
ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionKind,
|
||||
InstructionValue, LValue, LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue,
|
||||
ArrayElement, BlockKind, BranchTerminal, Environment, ForTerminal, Function, GotoKind,
|
||||
Identifier, InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal, Place,
|
||||
PrimitiveValue, TerminalValue,
|
||||
};
|
||||
|
||||
use crate::builder::{Binding, Builder};
|
||||
use crate::builder::{Binding, Builder, LoopScope};
|
||||
|
||||
/// Converts a React function in ESTree format into HIR. Returns the HIR
|
||||
/// if it was constructed sucessfully, otherwise a list of diagnostics
|
||||
@@ -35,7 +36,7 @@ pub fn build<'a>(
|
||||
}),
|
||||
);
|
||||
builder.terminate(
|
||||
TerminalValue::ReturnTerminal(hir::ReturnTerminal {
|
||||
TerminalValue::Return(hir::ReturnTerminal {
|
||||
value: implicit_return_value,
|
||||
}),
|
||||
hir::BlockKind::Block,
|
||||
@@ -55,7 +56,7 @@ fn lower_statement<'a>(
|
||||
env: &'a Environment<'a>,
|
||||
builder: &mut Builder<'a>,
|
||||
stmt: Statement,
|
||||
_label: Option<String<'a>>,
|
||||
label: Option<String<'a>>,
|
||||
) -> Result<(), Diagnostic> {
|
||||
match stmt {
|
||||
Statement::BlockStatement(stmt) => {
|
||||
@@ -66,7 +67,7 @@ fn lower_statement<'a>(
|
||||
Statement::BreakStatement(stmt) => {
|
||||
let block = builder.resolve_break(stmt.label.as_ref())?;
|
||||
builder.terminate(
|
||||
TerminalValue::GotoTerminal(hir::GotoTerminal {
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block,
|
||||
kind: GotoKind::Break,
|
||||
}),
|
||||
@@ -76,7 +77,7 @@ fn lower_statement<'a>(
|
||||
Statement::ContinueStatement(stmt) => {
|
||||
let block = builder.resolve_continue(stmt.label.as_ref())?;
|
||||
builder.terminate(
|
||||
TerminalValue::GotoTerminal(hir::GotoTerminal {
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block,
|
||||
kind: GotoKind::Continue,
|
||||
}),
|
||||
@@ -95,7 +96,7 @@ fn lower_statement<'a>(
|
||||
),
|
||||
};
|
||||
builder.terminate(
|
||||
TerminalValue::ReturnTerminal(hir::ReturnTerminal { value }),
|
||||
TerminalValue::Return(hir::ReturnTerminal { value }),
|
||||
BlockKind::Block,
|
||||
);
|
||||
}
|
||||
@@ -158,7 +159,7 @@ fn lower_statement<'a>(
|
||||
|
||||
let consequent_block = builder.enter(BlockKind::Block, |builder| {
|
||||
lower_statement(env, builder, consequent, None).unwrap();
|
||||
TerminalValue::GotoTerminal(hir::GotoTerminal {
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block: fallthrough_block.id,
|
||||
kind: GotoKind::Break,
|
||||
})
|
||||
@@ -168,14 +169,14 @@ fn lower_statement<'a>(
|
||||
if let Some(alternate) = alternate {
|
||||
lower_statement(env, builder, alternate, None).unwrap();
|
||||
}
|
||||
TerminalValue::GotoTerminal(hir::GotoTerminal {
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block: fallthrough_block.id,
|
||||
kind: GotoKind::Break,
|
||||
})
|
||||
});
|
||||
|
||||
let test = lower_expression_to_temporary(env, builder, test);
|
||||
let terminal = TerminalValue::IfTerminal(hir::IfTerminal {
|
||||
let terminal = TerminalValue::If(hir::IfTerminal {
|
||||
test,
|
||||
consequent: consequent_block,
|
||||
alternate: alternate_block,
|
||||
@@ -183,6 +184,80 @@ fn lower_statement<'a>(
|
||||
});
|
||||
builder.terminate_with_fallthrough(terminal, fallthrough_block);
|
||||
}
|
||||
Statement::ForStatement(stmt) => {
|
||||
let ForStatement {
|
||||
init,
|
||||
test,
|
||||
update,
|
||||
body,
|
||||
..
|
||||
} = *stmt;
|
||||
|
||||
// Block for the loop's test condition
|
||||
let test_block = builder.reserve(BlockKind::Loop);
|
||||
|
||||
// Block for code following the loop
|
||||
let fallthrough_block = builder.reserve(BlockKind::Block);
|
||||
|
||||
let init_block = builder.enter(BlockKind::Loop, |builder| {
|
||||
if let Some(ForInit::VariableDeclaration(decl)) = init {
|
||||
lower_statement(env, builder, Statement::VariableDeclaration(decl), None)
|
||||
.unwrap();
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block: test_block.id,
|
||||
kind: GotoKind::Break,
|
||||
})
|
||||
} else {
|
||||
panic!("Expected for statement to have a variable declaration initializer")
|
||||
}
|
||||
});
|
||||
|
||||
let update_block = update.map(|update| {
|
||||
builder.enter(BlockKind::Loop, |builder| {
|
||||
lower_expression_to_temporary(env, builder, update);
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block: test_block.id,
|
||||
kind: GotoKind::Break,
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let body_block = builder.enter(BlockKind::Block, |builder| {
|
||||
let loop_ = LoopScope {
|
||||
label,
|
||||
continue_block: update_block.unwrap_or(test_block.id),
|
||||
break_block: fallthrough_block.id,
|
||||
};
|
||||
builder.enter_loop(loop_, |builder| {
|
||||
lower_statement(env, builder, body, None).unwrap();
|
||||
TerminalValue::Goto(hir::GotoTerminal {
|
||||
block: update_block.unwrap_or(test_block.id),
|
||||
kind: GotoKind::Continue,
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let terminal = TerminalValue::For(ForTerminal {
|
||||
body: body_block,
|
||||
init: init_block,
|
||||
test: test_block.id,
|
||||
fallthrough: fallthrough_block.id,
|
||||
update: update_block,
|
||||
});
|
||||
builder.terminate_with_fallthrough(terminal, test_block);
|
||||
|
||||
if let Some(test) = test {
|
||||
let test_value = lower_expression_to_temporary(env, builder, test);
|
||||
let terminal = TerminalValue::Branch(BranchTerminal {
|
||||
test: test_value,
|
||||
consequent: body_block,
|
||||
alternate: fallthrough_block.id,
|
||||
});
|
||||
builder.terminate_with_fallthrough(terminal, fallthrough_block);
|
||||
} else {
|
||||
panic!("Expected for statement to have a tesst block");
|
||||
}
|
||||
}
|
||||
_ => todo!("Lower {stmt:#?}"),
|
||||
}
|
||||
Ok(())
|
||||
@@ -240,6 +315,7 @@ fn lower_expression<'a>(
|
||||
.collect_in(env.allocator);
|
||||
InstructionValue::Array(hir::Array { elements })
|
||||
}
|
||||
|
||||
ExpressionLike::AssignmentExpression(expr) => match expr.operator {
|
||||
estree::AssignmentOperator::Equals => {
|
||||
let right = lower_expression_to_temporary(env, builder, expr.right);
|
||||
@@ -248,6 +324,22 @@ fn lower_expression<'a>(
|
||||
_ => todo!("lower assignment expr {:#?}", expr),
|
||||
},
|
||||
|
||||
ExpressionLike::BinaryExpression(expr) => {
|
||||
let BinaryExpression {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
..
|
||||
} = *expr;
|
||||
let left = lower_expression_to_temporary(env, builder, left);
|
||||
let right = lower_expression_to_temporary(env, builder, right);
|
||||
InstructionValue::Binary(hir::Binary {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
})
|
||||
}
|
||||
|
||||
// Cases that cannot appear in expression position but which are included in ExpressionLike
|
||||
// to make serialization easier
|
||||
ExpressionLike::SpreadElement(_) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use std::{cell::RefCell, collections::HashSet, rc::Rc};
|
||||
|
||||
use hir::{
|
||||
@@ -27,6 +27,8 @@ pub(crate) struct Builder<'a> {
|
||||
wip: WipBlock<'a>,
|
||||
|
||||
id_gen: InstructionIdGenerator,
|
||||
|
||||
scopes: Vec<'a, ControlFlowScope<'a>>,
|
||||
}
|
||||
|
||||
pub(crate) struct WipBlock<'a> {
|
||||
@@ -35,6 +37,48 @@ pub(crate) struct WipBlock<'a> {
|
||||
pub instructions: Vec<'a, Instruction<'a>>,
|
||||
}
|
||||
|
||||
pub(crate) enum Binding<'a> {
|
||||
Local(Identifier<'a>),
|
||||
Module(Identifier<'a>),
|
||||
Global,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
enum ControlFlowScope<'a> {
|
||||
Loop(LoopScope<'a>),
|
||||
// Switch(SwitchScope<'a>),
|
||||
Label(LabelScope<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) struct LoopScope<'a> {
|
||||
pub label: Option<String<'a>>,
|
||||
pub continue_block: BlockId,
|
||||
pub break_block: BlockId,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) struct LabelScope<'a> {
|
||||
pub label: String<'a>,
|
||||
pub block: BlockId,
|
||||
}
|
||||
|
||||
impl<'a> ControlFlowScope<'a> {
|
||||
fn label(&self) -> Option<&String<'a>> {
|
||||
match self {
|
||||
Self::Loop(scope) => scope.label.as_ref(),
|
||||
Self::Label(scope) => Some(&scope.label),
|
||||
}
|
||||
}
|
||||
|
||||
fn break_block(&self) -> BlockId {
|
||||
match self {
|
||||
Self::Loop(scope) => scope.break_block,
|
||||
Self::Label(scope) => scope.block,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Builder<'a> {
|
||||
pub(crate) fn new(environment: &'a Environment<'a>) -> Self {
|
||||
let entry = environment.next_block_id();
|
||||
@@ -49,6 +93,7 @@ impl<'a> Builder<'a> {
|
||||
entry,
|
||||
wip: current,
|
||||
id_gen: InstructionIdGenerator::new(),
|
||||
scopes: Vec::new_in(&environment.allocator),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +200,17 @@ impl<'a> Builder<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn enter_loop<F>(&mut self, scope: LoopScope<'a>, f: F) -> TerminalValue<'a>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> TerminalValue<'a>,
|
||||
{
|
||||
self.scopes.push(ControlFlowScope::Loop(scope.clone()));
|
||||
let terminal = f(self);
|
||||
let last = self.scopes.pop().unwrap();
|
||||
assert_eq!(last, ControlFlowScope::Loop(scope));
|
||||
terminal
|
||||
}
|
||||
|
||||
/// Returns a new temporary identifier
|
||||
pub(crate) fn make_temporary(&self) -> hir::Identifier<'a> {
|
||||
hir::Identifier {
|
||||
@@ -173,9 +229,21 @@ impl<'a> Builder<'a> {
|
||||
/// provided but cannot be resolved.
|
||||
pub(crate) fn resolve_break(
|
||||
&self,
|
||||
_label: Option<&estree::Identifier>,
|
||||
label: Option<&estree::Identifier>,
|
||||
) -> Result<BlockId, Diagnostic> {
|
||||
todo!()
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match (label, scope.label()) {
|
||||
// If this is an unlabeled break, return the most recent break target
|
||||
(None, _) => return Ok(scope.break_block()),
|
||||
// If the break is labeled and matches the current scope, return its break target
|
||||
(Some(label), Some(scope_label)) if &label.name == scope_label => {
|
||||
return Ok(scope.break_block());
|
||||
}
|
||||
// Otherwise keep searching
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
|
||||
/// Resolves the target for the given continue label (if present), or returns the default
|
||||
@@ -183,9 +251,36 @@ impl<'a> Builder<'a> {
|
||||
/// provided but cannot be resolved.
|
||||
pub(crate) fn resolve_continue(
|
||||
&self,
|
||||
_label: Option<&estree::Identifier>,
|
||||
label: Option<&estree::Identifier>,
|
||||
) -> Result<BlockId, Diagnostic> {
|
||||
todo!()
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match scope {
|
||||
ControlFlowScope::Loop(scope) => {
|
||||
match (label, &scope.label) {
|
||||
// If this is an unlabeled continue, return the first matching loop
|
||||
(None, _) => return Ok(scope.continue_block),
|
||||
// If the continue is labeled and matches the current scope, return its continue target
|
||||
(Some(label), Some(scope_label))
|
||||
if label.name.as_str() == scope_label.as_str() =>
|
||||
{
|
||||
return Ok(scope.continue_block);
|
||||
}
|
||||
// Otherwise keep searching
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match (label, scope.label()) {
|
||||
(Some(label), Some(scope_label)) if label.name.as_str() == scope_label => {
|
||||
// Error, the continue referred to a label that is not a loop
|
||||
return Err(());
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_binding(
|
||||
@@ -206,12 +301,6 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Binding<'a> {
|
||||
Local(Identifier<'a>),
|
||||
Module(Identifier<'a>),
|
||||
Global,
|
||||
}
|
||||
|
||||
/// Modifies the HIR to put the blocks in reverse postorder, with predecessors before
|
||||
/// successors (except for the case of loops)
|
||||
fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
|
||||
@@ -230,20 +319,24 @@ fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
|
||||
let block = hir.block(block_id);
|
||||
let terminal = &block.terminal;
|
||||
match &terminal.value {
|
||||
TerminalValue::IfTerminal(terminal) => {
|
||||
TerminalValue::Branch(terminal) => {
|
||||
visit(terminal.alternate, hir, visited, postorder);
|
||||
visit(terminal.consequent, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::ForTerminal(terminal) => {
|
||||
TerminalValue::If(terminal) => {
|
||||
visit(terminal.alternate, hir, visited, postorder);
|
||||
visit(terminal.consequent, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::For(terminal) => {
|
||||
visit(terminal.init, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::DoWhileTerminal(terminal) => {
|
||||
TerminalValue::DoWhile(terminal) => {
|
||||
visit(terminal.body, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::GotoTerminal(terminal) => {
|
||||
TerminalValue::Goto(terminal) => {
|
||||
visit(terminal.block, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::ReturnTerminal(..) => { /* no-op */ }
|
||||
TerminalValue::Return(..) => { /* no-op */ }
|
||||
}
|
||||
postorder.push(block_id);
|
||||
}
|
||||
@@ -263,7 +356,7 @@ fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value {
|
||||
if let TerminalValue::For(terminal) = &mut block.terminal.value {
|
||||
if let Some(update) = terminal.update {
|
||||
if !block_ids.contains(&update) {
|
||||
terminal.update = None;
|
||||
@@ -297,9 +390,9 @@ fn remove_unreachable_do_while_statements<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
if let TerminalValue::DoWhileTerminal(terminal) = &mut block.terminal.value {
|
||||
if let TerminalValue::DoWhile(terminal) = &mut block.terminal.value {
|
||||
if !block_ids.contains(&terminal.test) {
|
||||
block.terminal.value = TerminalValue::GotoTerminal(hir::GotoTerminal {
|
||||
block.terminal.value = TerminalValue::Goto(hir::GotoTerminal {
|
||||
block: terminal.body,
|
||||
kind: GotoKind::Break,
|
||||
});
|
||||
@@ -353,7 +446,7 @@ fn mark_predecessors<'a>(hir: &mut HIR<'a>) {
|
||||
|
||||
fn invariant<F>(cond: bool, f: F) -> Result<(), Diagnostic>
|
||||
where
|
||||
F: FnOnce() -> String,
|
||||
F: FnOnce() -> std::string::String,
|
||||
{
|
||||
if !cond {
|
||||
let msg = f();
|
||||
|
||||
@@ -6,8 +6,8 @@ use swc_core::common::errors::Handler;
|
||||
use swc_core::common::source_map::Pos;
|
||||
use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS};
|
||||
use swc_core::ecma::ast::{
|
||||
AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, MemberExpr, ModuleItem, Pat,
|
||||
PatOrExpr, Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
|
||||
AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, MemberExpr, MemberProp,
|
||||
ModuleItem, Pat, PatOrExpr, Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
|
||||
};
|
||||
use swc_core::ecma::parser::Syntax;
|
||||
use swc_core::ecma::transforms::base::resolver;
|
||||
@@ -366,6 +366,9 @@ fn convert_expression(cx: &Context, expr: &Expr) -> estree::ExpressionLike {
|
||||
range: convert_span(&expr.span),
|
||||
}))
|
||||
}
|
||||
Expr::Member(expr) => {
|
||||
estree::ExpressionLike::MemberExpression(Box::new(convert_member_expression(cx, expr)))
|
||||
}
|
||||
_ => todo!("translate expression {:#?}", expr),
|
||||
}
|
||||
}
|
||||
@@ -376,22 +379,40 @@ fn convert_assignment_target(cx: &Context, target: &PatOrExpr) -> estree::Assign
|
||||
estree::AssignmentTarget::Pattern(Box::new(convert_pattern(cx, target)))
|
||||
}
|
||||
PatOrExpr::Expr(target) => {
|
||||
if let Expr::Member(target) = target.as_ref() {
|
||||
estree::AssignmentTarget::MemberExpression(Box::new(convert_member_expression(
|
||||
cx, target,
|
||||
)))
|
||||
} else {
|
||||
panic!(
|
||||
"Invalid input, expected either a pattern or member expression, got {:#?}",
|
||||
target
|
||||
)
|
||||
match target.as_ref() {
|
||||
Expr::Member(target) => estree::AssignmentTarget::MemberExpression(Box::new(
|
||||
convert_member_expression(cx, target),
|
||||
)),
|
||||
Expr::Ident(target) => estree::AssignmentTarget::Pattern(Box::new(
|
||||
estree::Pattern::Identifier(Box::new(convert_identifier(cx, target))),
|
||||
)),
|
||||
_ => {
|
||||
panic!("Expected assignment target to be member expression or identifier, got {:#?}", target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_member_expression(_cx: &Context, _expr: &MemberExpr) -> estree::MemberExpression {
|
||||
todo!("convert member expression")
|
||||
fn convert_member_expression(cx: &Context, expr: &MemberExpr) -> estree::MemberExpression {
|
||||
let (is_computed, property) = match &expr.prop {
|
||||
MemberProp::Ident(prop) => (
|
||||
false,
|
||||
estree::ExpressionLike::Identifier(Box::new(convert_identifier(cx, prop))),
|
||||
),
|
||||
MemberProp::Computed(prop) => (true, convert_expression(cx, &prop.expr)),
|
||||
_ => {
|
||||
panic!("PrivateName member expression properties are not supported")
|
||||
}
|
||||
};
|
||||
estree::MemberExpression {
|
||||
object: convert_expression(cx, &expr.obj),
|
||||
property,
|
||||
is_computed,
|
||||
is_optional: false,
|
||||
loc: None,
|
||||
range: convert_span(&expr.span),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use static_assertions::assert_eq_size;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{fmt::Display, num::NonZeroU32};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SourceLocation {
|
||||
@@ -689,6 +689,17 @@ pub enum BinaryOperator {
|
||||
Instanceof,
|
||||
}
|
||||
|
||||
impl Display for BinaryOperator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let name = match self {
|
||||
Self::Plus => "+",
|
||||
Self::LessThan => "<",
|
||||
_ => todo!("display for operator: {:#?}", self),
|
||||
};
|
||||
f.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CallExpression {
|
||||
#[serde(rename = "optional")]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
function foo() {
|
||||
let x = 0;
|
||||
for (let i = 0; i < 10; i = i + 1) {
|
||||
x = x + i;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
source: crates/fixtures/tests/fixtures_test.rs
|
||||
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
|
||||
input_file: crates/fixtures/tests/fixtures/for-statement.js
|
||||
---
|
||||
Input:
|
||||
function foo() {
|
||||
let x = 0;
|
||||
for (let i = 0; i < 10; i = i + 1) {
|
||||
x = x + i;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
Output:
|
||||
entry bb0
|
||||
bb0
|
||||
[0] unknown $0 = 0
|
||||
[1] unknown $2 = StoreLocal Let unknown x$1 = unknown $0
|
||||
[2] For init=bb3 test=bb1 update=bb4 body=bb5 fallthrough=bb2
|
||||
bb3
|
||||
[3] unknown $3 = 0
|
||||
[4] unknown $5 = StoreLocal Let unknown i$4 = unknown $3
|
||||
[5] Goto bb1
|
||||
bb1
|
||||
[6] unknown $14 = LoadLocal unknown i$4
|
||||
[7] unknown $15 = 10
|
||||
[8] unknown $16 = Binary unknown $14 < unknown $15
|
||||
[9] Branch unknown $16 consequent=bb5 alternate=bb2
|
||||
bb5
|
||||
[10] unknown $10 = LoadLocal unknown x$1
|
||||
[11] unknown $11 = LoadLocal unknown i$4
|
||||
[12] unknown $12 = Binary unknown $10 + unknown $11
|
||||
[13] unknown $13 = StoreLocal Reassign unknown x$1 = unknown $12
|
||||
[14] Goto bb4
|
||||
bb4
|
||||
[15] unknown $6 = LoadLocal unknown i$4
|
||||
[16] unknown $7 = 1
|
||||
[17] unknown $8 = Binary unknown $6 + unknown $7
|
||||
[18] unknown $9 = StoreLocal Reassign unknown i$4 = unknown $8
|
||||
[19] Goto bb1
|
||||
bb2
|
||||
[20] unknown $17 = LoadLocal unknown x$1
|
||||
[21] Return unknown $17
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{cell::RefCell, fmt::Display, rc::Rc};
|
||||
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use estree::BinaryOperator;
|
||||
|
||||
use crate::{IdentifierId, InstructionId, ScopeId, Type};
|
||||
|
||||
@@ -15,7 +16,7 @@ pub struct Instruction<'a> {
|
||||
pub enum InstructionValue<'a> {
|
||||
Array(Array<'a>),
|
||||
// Await(Await<'a>),
|
||||
// Binary(Binary<'a>),
|
||||
Binary(Binary<'a>),
|
||||
// Call(Call<'a>),
|
||||
// ComputedDelete(ComputedDelete<'a>),
|
||||
// ComputedLoad(ComputedLoad<'a>),
|
||||
@@ -59,6 +60,13 @@ pub enum ArrayElement<'a> {
|
||||
Spread(Place<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Binary<'a> {
|
||||
pub left: Place<'a>,
|
||||
pub operator: BinaryOperator,
|
||||
pub right: Place<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Primitive<'a> {
|
||||
pub value: PrimitiveValue<'a>,
|
||||
|
||||
@@ -90,6 +90,12 @@ impl<'a> Print for InstructionValue<'a> {
|
||||
write!(out, "DeclareLocal ")?;
|
||||
value.lvalue.print(out)?;
|
||||
}
|
||||
InstructionValue::Binary(value) => {
|
||||
write!(out, "Binary ")?;
|
||||
value.left.print(out)?;
|
||||
write!(out, " {} ", value.operator)?;
|
||||
value.right.print(out)?;
|
||||
}
|
||||
_ => write!(out, "{:?}", self)?,
|
||||
}
|
||||
Ok(())
|
||||
@@ -146,14 +152,14 @@ impl<'a> Print for Terminal<'a> {
|
||||
impl<'a> Print for TerminalValue<'a> {
|
||||
fn print(&self, out: &mut impl Write) -> Result {
|
||||
match self {
|
||||
TerminalValue::ReturnTerminal(terminal) => {
|
||||
TerminalValue::Return(terminal) => {
|
||||
write!(out, "Return ")?;
|
||||
terminal.value.print(out)?;
|
||||
}
|
||||
TerminalValue::GotoTerminal(terminal) => {
|
||||
TerminalValue::Goto(terminal) => {
|
||||
write!(out, "Goto {}", terminal.block)?;
|
||||
}
|
||||
TerminalValue::IfTerminal(terminal) => {
|
||||
TerminalValue::If(terminal) => {
|
||||
write!(out, "If ")?;
|
||||
terminal.test.print(out)?;
|
||||
write!(
|
||||
@@ -167,6 +173,29 @@ impl<'a> Print for TerminalValue<'a> {
|
||||
}
|
||||
)?;
|
||||
}
|
||||
TerminalValue::Branch(terminal) => {
|
||||
write!(out, "Branch ")?;
|
||||
terminal.test.print(out)?;
|
||||
write!(
|
||||
out,
|
||||
" consequent={} alternate={}",
|
||||
terminal.consequent, terminal.alternate,
|
||||
)?;
|
||||
}
|
||||
TerminalValue::For(terminal) => {
|
||||
write!(
|
||||
out,
|
||||
"For init={} test={} update={} body={} fallthrough={}",
|
||||
terminal.init,
|
||||
terminal.test,
|
||||
match terminal.update {
|
||||
Some(fallthrough) => format!("{fallthrough}"),
|
||||
None => "<none>".to_string(),
|
||||
},
|
||||
terminal.body,
|
||||
terminal.fallthrough,
|
||||
)?;
|
||||
}
|
||||
_ => write!(out, "{:?}", self)?,
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -10,22 +10,22 @@ pub struct Terminal<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TerminalValue<'a> {
|
||||
// BranchTerminal(BranchTerminal),
|
||||
DoWhileTerminal(DoWhileTerminal),
|
||||
// ForOfTerminal(ForOfTerminal),
|
||||
ForTerminal(ForTerminal),
|
||||
GotoTerminal(GotoTerminal),
|
||||
IfTerminal(IfTerminal<'a>),
|
||||
// LabelTerminal(LabelTerminal),
|
||||
// LogicalTerminal(LogicalTerminal),
|
||||
// OptionalTerminal(OptionalTerminal),
|
||||
ReturnTerminal(ReturnTerminal<'a>),
|
||||
// SequenceTerminal(SequenceTerminal),
|
||||
// SwitchTerminal(SwitchTerminal),
|
||||
// TernaryTerminal(TernaryTerminal),
|
||||
// ThrowTerminal(ThrowTerminal),
|
||||
// UnsupportedTerminal(UnsupportedTerminal),
|
||||
// WhileTerminal(WhileTerminal),
|
||||
Branch(BranchTerminal<'a>),
|
||||
DoWhile(DoWhileTerminal),
|
||||
// ForOf(ForOfTerminal),
|
||||
For(ForTerminal),
|
||||
Goto(GotoTerminal),
|
||||
If(IfTerminal<'a>),
|
||||
// Label(LabelTerminal),
|
||||
// Logical(LogicalTerminal),
|
||||
// Optional(OptionalTerminal),
|
||||
Return(ReturnTerminal<'a>),
|
||||
// Sequence(SequenceTerminal),
|
||||
// Switch(SwitchTerminal),
|
||||
// Ternary(TernaryTerminal),
|
||||
// Throw(ThrowTerminal),
|
||||
// Unsupported(UnsupportedTerminal),
|
||||
// While(WhileTerminal),
|
||||
}
|
||||
|
||||
impl<'a> TerminalValue<'a> {
|
||||
@@ -34,43 +34,53 @@ impl<'a> TerminalValue<'a> {
|
||||
F: Fn(BlockId) -> Option<BlockId>,
|
||||
{
|
||||
match self {
|
||||
Self::IfTerminal(terminal) => {
|
||||
Self::If(terminal) => {
|
||||
terminal.fallthrough = match terminal.fallthrough {
|
||||
Some(fallthrough) => f(fallthrough),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Self::DoWhileTerminal(DoWhileTerminal { fallthrough, .. })
|
||||
| Self::ForTerminal(ForTerminal { fallthrough, .. }) => {
|
||||
Self::DoWhile(DoWhileTerminal { fallthrough, .. })
|
||||
| Self::For(ForTerminal { fallthrough, .. }) => {
|
||||
// statically detect if fallthrough is changed to Option so
|
||||
// that we can update to map the fallthrough w f()
|
||||
let _: BlockId = *fallthrough;
|
||||
}
|
||||
Self::GotoTerminal(_) | Self::ReturnTerminal(_) => {}
|
||||
Self::Branch(_) | Self::Goto(_) | Self::Return(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn successors(&self) -> Vec<BlockId> {
|
||||
match self {
|
||||
Self::IfTerminal(terminal) => {
|
||||
Self::If(terminal) => {
|
||||
vec![terminal.consequent, terminal.alternate]
|
||||
}
|
||||
Self::ForTerminal(terminal) => {
|
||||
Self::Branch(terminal) => {
|
||||
vec![terminal.consequent, terminal.alternate]
|
||||
}
|
||||
Self::For(terminal) => {
|
||||
vec![terminal.init]
|
||||
}
|
||||
Self::DoWhileTerminal(terminal) => {
|
||||
Self::DoWhile(terminal) => {
|
||||
vec![terminal.body]
|
||||
}
|
||||
Self::GotoTerminal(terminal) => {
|
||||
Self::Goto(terminal) => {
|
||||
vec![terminal.block]
|
||||
}
|
||||
Self::ReturnTerminal(_) => {
|
||||
Self::Return(_) => {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BranchTerminal<'a> {
|
||||
pub test: Place<'a>,
|
||||
pub consequent: BlockId,
|
||||
pub alternate: BlockId,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GotoTerminal {
|
||||
pub block: BlockId,
|
||||
|
||||
Reference in New Issue
Block a user