For-of loop (#85)

Implements `es2015::for_of`
This commit is contained in:
강동윤 2018-12-21 21:39:42 +09:00 committed by GitHub
parent f0e4598f41
commit 145d6f39eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 739 additions and 8 deletions

View File

@ -45,7 +45,7 @@ New generation javascript to old-days javascript.
- [x] computed-properties
- [x] destructuring
- [x] duplicate-keys
- [ ] for-of
- [x] for-of
- [x] function-name
- [x] instanceof
- [x] literals
@ -77,11 +77,13 @@ New generation javascript to old-days javascript.
The benchmarks were run on Macbook pro, dual core, 2.3GHz Intel Core i5, 16 GB ram
| | performance |
| --------------------- |:--------------------------------------:|
| swc (ffi) | 1,086 ops/sec ±0.77% (84 runs sampled) |
| swc-optimize (ffi) | 1,060 ops/sec ±0.63% (87 runs sampled) |
| babel | 65.72 ops/sec ±6.45% (62 runs sampled) |
| | performance |
| ------------------------ |:--------------------------------------:|
| swc (ffi) | 1,086 ops/sec ±0.77% (84 runs sampled) |
| swc-optimize (ffi) | 1,060 ops/sec ±0.63% (87 runs sampled) |
| swc (ffi, simd) | 1,295 ops/sec ±0.87% (89 runs sampled) |
| swc-optimize (ffi, simd) | 1,270 ops/sec ±0.24% (89 runs sampled) |
| babel | 65.72 ops/sec ±6.45% (62 runs sampled) |
## Contributing

View File

@ -0,0 +1,432 @@
use crate::util::{ExprFactory, StmtLike};
use ast::*;
use swc_common::{Fold, FoldWith, Mark, Spanned, DUMMY_SP};
#[cfg(test)]
mod tests;
/// `@babel/plugin-transform-for-of`
///
/// ## In
///
/// ```js
/// for (var i of foo) {}
/// ```
///
/// ## Out
///
/// ```js
/// var _iteratorNormalCompletion = true;
/// var _didIteratorError = false;
/// var _iteratorError = undefined;
///
/// try {
/// for (var _iterator = foo[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
/// var i = _step.value;
/// }
/// } catch (err) {
/// _didIteratorError = true;
/// _iteratorError = err;
/// } finally {
/// try {
/// if (!_iteratorNormalCompletion && _iterator.return != null) {
/// _iterator.return();
/// }
/// } finally {
/// if (_didIteratorError) {
/// throw _iteratorError;
/// }
/// }
/// }
/// ```
pub fn for_of() -> impl Fold<Module> {
ForOf
}
struct ForOf;
/// Real folder.
#[derive(Default)]
struct Actual {
///```js
/// var _iteratorNormalCompletion = true;
/// var _didIteratorError = false;
/// var _iteratorError = undefined;
/// ```
top_level_vars: Vec<VarDeclarator>,
}
impl Actual {
fn fold_for_stmt(
&mut self,
label: Option<Ident>,
ForOfStmt {
span,
await_token,
left,
right,
body,
}: ForOfStmt,
) -> Stmt {
assert!(await_token.is_none());
let var_span = left.span().apply_mark(Mark::fresh(Mark::root()));
// TODO(kdy): convert to normal for loop if rhs is array
// TODO(kdy): Type annotation to determine if rhs is array
let mut body = match *body {
Stmt::Block(block) => block,
body => BlockStmt {
span: DUMMY_SP,
stmts: vec![body],
},
};
let step = quote_ident!(var_span, "_step");
let step_value = box Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: ExprOrSuper::Expr(box Expr::Ident(step.clone())),
computed: false,
prop: box Expr::Ident(quote_ident!("value")),
});
body.stmts.insert(
0,
match left {
VarDeclOrPat::VarDecl(mut var) => {
assert!(var.decls.len() == 1);
Stmt::Decl(Decl::Var(VarDecl {
span: var.span,
kind: var.kind,
decls: vec![VarDeclarator {
init: Some(step_value),
..var.decls.pop().unwrap()
}],
}))
}
VarDeclOrPat::Pat(pat) => Stmt::Expr(box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box pat),
op: op!("="),
right: step_value,
})),
},
);
let iterator = quote_ident!(var_span, "_iterator");
// `_iterator.return`
let iterator_return = box Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: ExprOrSuper::Expr(box Expr::Ident(iterator.clone())),
computed: false,
prop: box Expr::Ident(quote_ident!("return")),
});
let normal_completion_ident = Ident::new("_iteratorNormalCompletion".into(), var_span);
self.top_level_vars.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(normal_completion_ident.clone()),
init: Some(box Expr::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: true,
}))),
});
let error_flag_ident = Ident::new("_didIteratorError".into(), var_span);
self.top_level_vars.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(error_flag_ident.clone()),
init: Some(box Expr::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: false,
}))),
});
let error_ident = Ident::new("_iteratorError".into(), var_span);
self.top_level_vars.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(error_ident.clone()),
init: Some(box Expr::Ident(Ident::new(js_word!("undefined"), DUMMY_SP))),
});
let for_stmt = ForStmt {
span,
init: Some(VarDeclOrExpr::VarDecl(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
decls: vec![
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(iterator.clone()),
init: Some(box Expr::Call(CallExpr {
span: DUMMY_SP,
callee: MemberExpr {
span: DUMMY_SP,
obj: ExprOrSuper::Expr(right),
computed: true,
prop: member_expr!(DUMMY_SP, Symbol.iterator),
}
.as_callee(),
args: vec![],
})),
},
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(step.clone()),
init: None,
},
],
})),
// !(_iteratorNormalCompletion = (_step = _iterator.next()).done)
test: Some(box Expr::Unary(UnaryExpr {
span: DUMMY_SP,
op: op!("!"),
arg: box Expr::Paren(ParenExpr {
span: DUMMY_SP,
expr: box Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: ExprOrSuper::Expr(box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(normal_completion_ident.clone())),
op: op!("="),
right: box Expr::Paren(ParenExpr {
span: DUMMY_SP,
expr: box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(step.clone())),
op: op!("="),
// `_iterator.next()`
right: box Expr::Call(CallExpr {
span: DUMMY_SP,
// `_iterator.next`
callee: MemberExpr {
span: DUMMY_SP,
computed: false,
obj: ExprOrSuper::Expr(box Expr::Ident(
iterator.clone(),
)),
prop: member_expr!(DUMMY_SP, next),
}
.as_callee(),
args: vec![],
}),
}),
}),
})),
computed: false,
prop: box Expr::Ident(quote_ident!("done")),
}),
}),
})),
// `_iteratorNormalCompletion = true`
update: Some(box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(normal_completion_ident.clone())),
op: op!("="),
right: box Expr::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: true,
})),
})),
body: box Stmt::Block(body),
}
.into();
let for_stmt = match label {
Some(label) => Stmt::Labeled(LabeledStmt {
span,
label,
body: box for_stmt,
}),
None => for_stmt,
};
Stmt::Try(TryStmt {
span: DUMMY_SP,
block: BlockStmt {
span: DUMMY_SP,
stmts: vec![for_stmt],
},
handler: Some(CatchClause {
span: DUMMY_SP,
param: Some(Pat::Ident(quote_ident!("err"))),
// _didIteratorError = true;
// _iteratorError = err;
body: BlockStmt {
span: DUMMY_SP,
stmts: vec![
// _didIteratorError = true;
Stmt::Expr(box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(error_flag_ident.clone())),
op: op!("="),
right: box Expr::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: true,
})),
})),
// _iteratorError = err;
Stmt::Expr(box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(error_ident.clone())),
op: op!("="),
right: box Expr::Ident(quote_ident!("err")),
})),
],
},
}),
finalizer: Some(BlockStmt {
span: DUMMY_SP,
stmts: vec![make_finally_block(
iterator_return,
&normal_completion_ident,
error_flag_ident,
error_ident,
)],
}),
})
}
}
impl Fold<Stmt> for Actual {
fn fold(&mut self, stmt: Stmt) -> Stmt {
match stmt {
Stmt::Labeled(LabeledStmt { span, label, body }) => {
// Handle label
match *body {
Stmt::ForOf(
stmt @ ForOfStmt {
await_token: None, ..
},
) => self.fold_for_stmt(Some(label), stmt),
_ => {
return Stmt::Labeled(LabeledStmt {
span,
label,
body: body.fold_children(self),
});
}
}
}
Stmt::ForOf(
stmt @ ForOfStmt {
await_token: None, ..
},
) => self.fold_for_stmt(None, stmt),
_ => stmt.fold_children(self),
}
}
}
/// ```js
///
/// try {
/// if (!_iteratorNormalCompletion && _iterator.return != null) {
/// _iterator.return();
/// }
/// } finally {
/// if (_didIteratorError) {
/// throw _iteratorError;
/// }
/// }
/// ```
fn make_finally_block(
iterator_return: Box<Expr>,
normal_completion_ident: &Ident,
error_flag_ident: Ident,
error_ident: Ident,
) -> Stmt {
Stmt::Try(TryStmt {
span: DUMMY_SP,
block: BlockStmt {
span: DUMMY_SP,
stmts: vec![
// if (!_iteratorNormalCompletion && _iterator.return !=
// null) {
// _iterator.return();
// }
Stmt::If(IfStmt {
span: DUMMY_SP,
test: box Expr::Bin(BinExpr {
span: DUMMY_SP,
left: box Expr::Unary(UnaryExpr {
span: DUMMY_SP,
op: op!("!"),
arg: box Expr::Ident(normal_completion_ident.clone()),
}),
op: op!("&&"),
right: box Expr::Bin(BinExpr {
span: DUMMY_SP,
left: iterator_return.clone(),
op: op!("!="),
right: box Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
}),
}),
cons: box Stmt::Block(BlockStmt {
span: DUMMY_SP,
stmts: vec![Stmt::Expr(box Expr::Call(CallExpr {
span: DUMMY_SP,
callee: iterator_return.as_callee(),
args: vec![],
}))],
}),
alt: None,
}),
],
},
handler: None,
finalizer: Some(BlockStmt {
span: DUMMY_SP,
stmts: vec![
// if (_didIteratorError) {
// throw _iteratorError;
// }
Stmt::If(IfStmt {
span: DUMMY_SP,
test: box Expr::Ident(error_flag_ident),
cons: box Stmt::Block(BlockStmt {
span: DUMMY_SP,
stmts: vec![Stmt::Throw(ThrowStmt {
span: DUMMY_SP,
arg: box Expr::Ident(error_ident),
})],
}),
alt: None,
}),
],
}),
})
}
impl<T: StmtLike> Fold<Vec<T>> for ForOf
where
Vec<T>: FoldWith<Self>,
{
fn fold(&mut self, stmts: Vec<T>) -> Vec<T> {
let stmts = stmts.fold_children(self);
let mut buf = Vec::with_capacity(stmts.len());
for stmt in stmts {
match stmt.try_into_stmt() {
Err(module_item) => buf.push(module_item),
Ok(stmt) => {
let mut folder = Actual::default();
let stmt = stmt.fold_with(&mut folder);
// Add variable declaration
// e.g. var ref
if !folder.top_level_vars.is_empty() {
buf.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
decls: folder.top_level_vars,
}))));
}
buf.push(T::from_stmt(stmt));
}
}
}
buf
}
}

View File

@ -0,0 +1,226 @@
use super::*;
test!(
ForOf,
spec_identifier,
r#"for (i of arr) {
}"#,
r#"var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
i = _step.value;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}"#,ok_if_code_eq
);
test!(ForOf, spec_ignore_cases, r#"for (var i of foo) {
switch (i) {
case 1:
break;
}
}"#, r#"var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
for (var _iterator = foo[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var i = _step.value;
switch (i) {
case 1:
break;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}"#, ok_if_code_eq);
test!(ForOf, spec_let, r#"for (let i of arr) {
}"#, r#"
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
let i = _step.value;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}"#,
ok_if_code_eq);
test!(ForOf, spec_member_expr, r#"for (obj.prop of arr) {
}"#, r#"var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
obj.prop = _step.value;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}"#,
ok_if_code_eq);
test!(ForOf, spec_multiple, r#"for (var i of arr) {
}
for (var i of numbers) {
}
"#, r#"var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var i = _step.value;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
try {
for (var _iterator1 = numbers[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true) {
var i = _step1.value;
}
} catch (err) {
_didIteratorError1 = true;
_iteratorError1 = err;
} finally {
try {
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
_iterator1.return();
}
} finally {
if (_didIteratorError1) {
throw _iteratorError1;
}
}
}"#, ok_if_code_eq);
test!(ForOf, spec_nested_label_for_of, r#"b: for (let c of d()) {
for (let e of f()) {
continue b;
}
}"#, r#"var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
b: for (var _iterator = d()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
let c = _step.value;
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
try {
for (var _iterator1 = f()[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true) {
let e = _step1.value;
continue b;
}
} catch (err) {
_didIteratorError1 = true;
_iteratorError1 = err;
} finally {
try {
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
_iterator1.return();
}
} finally {
if (_didIteratorError1) {
throw _iteratorError1;
}
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}"#, ok_if_code_eq);
test!(ForOf, spec_var, r#"for (var i of arr) {
}"#, r#"var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
try {
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var i = _step.value;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}"#, ok_if_code_eq);

View File

@ -1,8 +1,8 @@
pub use self::{
arrow::arrow, block_scoped_fn::BlockScopedFns, block_scoping::block_scoping, classes::Classes,
computed_props::computed_properties, destructuring::destructuring,
duplicate_keys::duplicate_keys, function_name::function_name, instanceof::InstanceOf,
parameters::parameters, shorthand_property::Shorthand, spread::Spread,
duplicate_keys::duplicate_keys, for_of::for_of, function_name::function_name,
instanceof::InstanceOf, parameters::parameters, shorthand_property::Shorthand, spread::Spread,
sticky_regex::StickyRegex, template_literal::TemplateLiteral, typeof_symbol::TypeOfSymbol,
};
use super::helpers::Helpers;
@ -17,6 +17,7 @@ mod classes;
mod computed_props;
mod destructuring;
mod duplicate_keys;
mod for_of;
mod function_name;
mod instanceof;
mod parameters;
@ -66,6 +67,7 @@ pub fn es2015(helpers: &Arc<Helpers>) -> impl Fold<Module> {
chain_at!(
Module,
stmts(helpers),
for_of(),
computed_properties(helpers.clone()),
destructuring(helpers.clone()),
block_scoping(),

View File

@ -283,4 +283,62 @@ mod tests {
);
}
#[test]
fn for_loop() {
test(
|tester| {
let mark1 = Mark::fresh(Mark::root());
let mark2 = Mark::fresh(mark1);
let mark3 = Mark::fresh(mark1);
Ok(vec![
tester
.parse_stmt("actual1.js", "for(var a=1;;) {}")?
.fold_with(&mut marker(&[("a", mark1)])),
tester
.parse_stmt("actual2.js", "for(var a of foo) {}")?
.fold_with(&mut marker(&[("a", mark2)])),
tester
.parse_stmt("actual3.js", "for(var a=3;;) {}")?
.fold_with(&mut marker(&[("a", mark3)])),
])
},
"
for(var a=1;;) {}
for(var a1 of foo) {}
for(var a2 = 3;;) {}
",
);
}
#[test]
fn try_for_loop() {
test(
|tester| {
let mark1 = Mark::fresh(Mark::root());
let mark2 = Mark::fresh(mark1);
let mark3 = Mark::fresh(mark1);
Ok(vec![
tester
.parse_stmt("actual1.js", "try { for(var a=1;;) {} } finally {}")?
.fold_with(&mut marker(&[("a", mark1)])),
tester
.parse_stmt("actual2.js", "for(var a of foo) {}")?
.fold_with(&mut marker(&[("a", mark2)])),
tester
.parse_stmt("actual3.js", "for(var a=3;;) {}")?
.fold_with(&mut marker(&[("a", mark3)])),
])
},
"
try {
for(var a=1;;) {}
} finally {
}
for(var a1 of foo) {}
for(var a2 = 3;;) {}
",
);
}
}

View File

@ -148,6 +148,17 @@ impl<'a, 'b, T: Traverse> Fold<Module> for ScopeAnalyzer<'a, 'b, T> {
}
}
impl<'a, 'b, T: Traverse> Fold<TryStmt> for ScopeAnalyzer<'a, 'b, T> {
fn fold(&mut self, node: TryStmt) -> TryStmt {
TryStmt {
span: node.span,
block: node.block.fold_children(self),
handler: node.handler.fold_with(self),
finalizer: node.finalizer.fold_children(self),
}
}
}
impl<'a, 'b, T: Traverse> Fold<BlockStmt> for ScopeAnalyzer<'a, 'b, T> {
fn fold(&mut self, node: BlockStmt) -> BlockStmt {
let node = {