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] computed-properties
- [x] destructuring - [x] destructuring
- [x] duplicate-keys - [x] duplicate-keys
- [ ] for-of - [x] for-of
- [x] function-name - [x] function-name
- [x] instanceof - [x] instanceof
- [x] literals - [x] literals
@ -78,9 +78,11 @@ New generation javascript to old-days javascript.
The benchmarks were run on Macbook pro, dual core, 2.3GHz Intel Core i5, 16 GB ram The benchmarks were run on Macbook pro, dual core, 2.3GHz Intel Core i5, 16 GB ram
| | performance | | | performance |
| --------------------- |:--------------------------------------:| | ------------------------ |:--------------------------------------:|
| swc (ffi) | 1,086 ops/sec ±0.77% (84 runs sampled) | | swc (ffi) | 1,086 ops/sec ±0.77% (84 runs sampled) |
| swc-optimize (ffi) | 1,060 ops/sec ±0.63% (87 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) | | babel | 65.72 ops/sec ±6.45% (62 runs sampled) |

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::{ pub use self::{
arrow::arrow, block_scoped_fn::BlockScopedFns, block_scoping::block_scoping, classes::Classes, arrow::arrow, block_scoped_fn::BlockScopedFns, block_scoping::block_scoping, classes::Classes,
computed_props::computed_properties, destructuring::destructuring, computed_props::computed_properties, destructuring::destructuring,
duplicate_keys::duplicate_keys, function_name::function_name, instanceof::InstanceOf, duplicate_keys::duplicate_keys, for_of::for_of, function_name::function_name,
parameters::parameters, shorthand_property::Shorthand, spread::Spread, instanceof::InstanceOf, parameters::parameters, shorthand_property::Shorthand, spread::Spread,
sticky_regex::StickyRegex, template_literal::TemplateLiteral, typeof_symbol::TypeOfSymbol, sticky_regex::StickyRegex, template_literal::TemplateLiteral, typeof_symbol::TypeOfSymbol,
}; };
use super::helpers::Helpers; use super::helpers::Helpers;
@ -17,6 +17,7 @@ mod classes;
mod computed_props; mod computed_props;
mod destructuring; mod destructuring;
mod duplicate_keys; mod duplicate_keys;
mod for_of;
mod function_name; mod function_name;
mod instanceof; mod instanceof;
mod parameters; mod parameters;
@ -66,6 +67,7 @@ pub fn es2015(helpers: &Arc<Helpers>) -> impl Fold<Module> {
chain_at!( chain_at!(
Module, Module,
stmts(helpers), stmts(helpers),
for_of(),
computed_properties(helpers.clone()), computed_properties(helpers.clone()),
destructuring(helpers.clone()), destructuring(helpers.clone()),
block_scoping(), 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> { impl<'a, 'b, T: Traverse> Fold<BlockStmt> for ScopeAnalyzer<'a, 'b, T> {
fn fold(&mut self, node: BlockStmt) -> BlockStmt { fn fold(&mut self, node: BlockStmt) -> BlockStmt {
let node = { let node = {