Skip to content

Commit

Permalink
Allow compiling async functions to generators
Browse files Browse the repository at this point in the history
This is done by setting `--async-functions=generator --generators=parse`.

Related to google#1231
Fixes google#1780
  • Loading branch information
arv committed Apr 4, 2016
1 parent 12432d7 commit a1dc260
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 16 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ INDIVIDUAL_RUNTIME_MODULES = \
src/runtime/classes.js \
src/runtime/generators.js \
src/runtime/async.js \
src/runtime/spawn.js \
src/runtime/template.js \
src/runtime/jsx.js \
#end runtime modules
Expand Down
3 changes: 1 addition & 2 deletions src/codegeneration/AlphaRenamer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { ScopeTransformer } from './ScopeTransformer.js';
import {ScopeTransformer} from './ScopeTransformer.js';
import {
FunctionDeclaration,
FunctionExpression
Expand Down Expand Up @@ -121,4 +121,3 @@ export class AlphaRenamer extends ScopeTransformer {
return new AlphaRenamer(varName, newName).transformAny(tree);
}
}

105 changes: 105 additions & 0 deletions src/codegeneration/AsyncToGeneratorTransformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2015 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {
FunctionBody,
FunctionDeclaration,
FunctionExpression,
Method,
YieldExpression
} from '../syntax/trees/ParseTrees.js';
import {ParenTrait} from './ParenTrait.js';
import {parseStatement} from './PlaceholderParser.js';
import {STAR} from '../syntax/TokenType.js';
import {TempVarTransformer} from './TempVarTransformer.js';
import FindArguments from './FindArguments.js';
import {
createIdentifierExpression,
createNullLiteral,
} from './ParseTreeFactory.js';
import {ARGUMENTS} from '../syntax/PredefinedName.js';

export class AsyncToGeneratorTransformer extends ParenTrait(TempVarTransformer) {
constructor(identifierGenerator, reporter, options) {
super(identifierGenerator, reporter, options);
this.inAsyncFunction_ = false;
}

transformFunctionDeclaration(tree) {
if (tree.isAsyncFunction()) {
return this.transformFunctionShared_(tree, FunctionDeclaration);
}
return super.transformFunctionDeclaration(tree);
}

transformFunctionExpression(tree) {
if (tree.isAsyncFunction()) {
return this.transformFunctionShared_(tree, FunctionExpression);
}
return super.transformFunctionExpression(tree);
}

transformFunctionShared_(tree, ctor) {
const parameterList = this.transformAny(tree.parameterList);
const typeAnnotation = this.transformAny(tree.typeAnnotation);
const annotations = this.transformList(tree.annotations);
const body = this.transformAsyncBody_(tree.body);
return new ctor(tree.location, tree.name, null,
parameterList, typeAnnotation, annotations, body);
}

transformAsyncBody_(body) {
const inAsyncFunction = this.inAsyncFunction_;
this.inAsyncFunction_ = true;
body = this.transformFunctionBody(body);

body = wrapBodyInSpawn(body);
this.inAsyncFunction_ = inAsyncFunction;
return body;
}

transformMethod(tree) {
if (tree.isAsyncFunction()) {
const name = this.transformAny(tree.name);
const parameterList = this.transformAny(tree.parameterList);
const typeAnnotation = this.transformAny(tree.typeAnnotation);
const annotations = this.transformList(tree.annotations);
const body = this.transformAsyncBody_(tree.body);
return new Method(tree.location, tree.isStatic, null, name,
parameterList, typeAnnotation, annotations, body,
tree.debugName);
}
return super.transformMethod(tree);
}

transformAwaitExpression(tree) {
if (this.inAsyncFunction_) {
const expression = this.transformAny(tree.expression);
return new YieldExpression(tree.location, expression, false);
}
return super.transformAwaitExpression(tree);
}
}

function wrapBodyInSpawn(body) {
const visitor = new FindArguments();
visitor.visitAny(body);
const hasArguments = visitor.found;
const argExpr = visitor.found ?
createIdentifierExpression(ARGUMENTS) :
createNullLiteral();
const statement = parseStatement
`return $traceurRuntime.spawn(this, ${argExpr}, function*() { ${body} });`
return new FunctionBody(body.location, [statement])
}
27 changes: 27 additions & 0 deletions src/codegeneration/FindArguments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2016 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {ARGUMENTS} from '../syntax/PredefinedName.js';
import {FindInFunctionScope} from './FindInFunctionScope.js';

/**
* This is used to find whether a function contains a reference to `arguments`.
*/
export default class FindArguments extends FindInFunctionScope {
visitIdentifierExpression(tree) {
if (tree.identifierToken.value === ARGUMENTS) {
this.found = true;
}
}
}
11 changes: 10 additions & 1 deletion src/codegeneration/FromOptionsTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {AnnotationsTransformer} from './AnnotationsTransformer.js';
import {ArrayComprehensionTransformer} from './ArrayComprehensionTransformer.js';
import {ArrowFunctionTransformer} from './ArrowFunctionTransformer.js';
import {AsyncGeneratorTransformPass} from './AsyncGeneratorTransformPass.js';
import {AsyncToGeneratorTransformer} from './AsyncToGeneratorTransformer.js';
import {BlockBindingTransformer} from './BlockBindingTransformer.js';
import {ClassTransformer} from './ClassTransformer.js';
import {ClosureModuleTransformer} from './ClosureModuleTransformer.js';
Expand Down Expand Up @@ -200,8 +201,16 @@ export class FromOptionsTransformer extends MultiTransformer {
});
}

// Async functions must come after all the parameter transformers.
if (transformOptions.asyncFunctions === 'generator'
&& !transformOptions.generators) {
append(AsyncToGeneratorTransformer);
}

// generator must come after for of, for on and rest parameters
if (transformOptions.generators || transformOptions.asyncFunctions)
if (transformOptions.generators ||
transformOptions.asyncFunctions &&
transformOptions.asyncFunctions !== 'generator')
append(GeneratorTransformPass);

if (transformOptions.symbols)
Expand Down
31 changes: 29 additions & 2 deletions src/codegeneration/ParenTrait.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ArgumentList,
ArrayLiteral,
BinaryExpression,
ConditionalExpression,
ExpressionStatement,
NewExpression,
ParenExpression,
Expand All @@ -29,6 +30,7 @@ import {
OBJECT_LITERAL,
OBJECT_PATTERN,
TEMPLATE_LITERAL_EXPRESSION,
YIELD_EXPRESSION
} from '../syntax/trees/ParseTreeType.js';

function wrap(tree) {
Expand Down Expand Up @@ -146,16 +148,41 @@ export function ParenTrait(ParseTreeTransformerClass) {
transformBinaryExpression(tree) {
let left = this.transformAny(tree.left);
let right = this.transformAny(tree.right);
if (left.type === COMMA_EXPRESSION) {
if (commaOrYield(left.type)) {
left = wrap(left);
}
if (right.type === COMMA_EXPRESSION) {
if (commaOrYield(right.type)) {
right = wrap(right);
}
if (left === tree.left && right === tree.right) {
return tree;
}
return new BinaryExpression(tree.location, left, tree.operator, right);
}

transformConditionalExpression(tree) {
let condition = this.transformAny(tree.condition);
let left = this.transformAny(tree.left);
let right = this.transformAny(tree.right);
if (commaOrYield(condition.type)) {
condition = wrap(condition);
}
if (left.type == COMMA_EXPRESSION) {
left = wrap(left);
}
if (right.type == COMMA_EXPRESSION) {
right = wrap(right);
}
if (condition === tree.condition &&
left === tree.left &&
right === tree.right) {
return tree;
}
return new ConditionalExpression(tree.location, condition, left, right);
}
};
}

function commaOrYield(type) {
return type === COMMA_EXPRESSION || type == YIELD_EXPRESSION;
}
7 changes: 6 additions & 1 deletion src/codegeneration/PlaceholderParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ARGUMENT_LIST,
BLOCK,
EXPRESSION_STATEMENT,
FUNCTION_BODY,
IDENTIFIER_EXPRESSION
} from '../syntax/trees/ParseTreeType.js';
import {IdentifierToken} from '../syntax/IdentifierToken.js';
Expand Down Expand Up @@ -260,8 +261,10 @@ export class PlaceholderTransformer extends ParseTreeTransformer {
this.transformIdentifierExpression(tree.expression);
if (transformedExpression === tree.expression)
return tree;
if (transformedExpression.isStatementListItem())
if (transformedExpression.isStatementListItem() ||
transformedExpression.type === FUNCTION_BODY) {
return transformedExpression;
}
return createExpressionStatement(transformedExpression);
}
return super.transformExpressionStatement(tree);
Expand All @@ -285,6 +288,8 @@ export class PlaceholderTransformer extends ParseTreeTransformer {
tree.statements[0].type === EXPRESSION_STATEMENT) {
let transformedStatement =
this.transformExpressionStatement(tree.statements[0]);
if (transformedStatement.type === FUNCTION_BODY)
return transformedStatement;
if (transformedStatement === tree.statements[0])
return tree;
if (transformedStatement.type === BLOCK)
Expand Down
3 changes: 3 additions & 0 deletions src/codegeneration/PureES6Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import {AnnotationsTransformer} from './AnnotationsTransformer.js';
import {AsyncToGeneratorTransformer} from './AsyncToGeneratorTransformer.js';
import {InlineES6ModuleTransformer} from './InlineES6ModuleTransformer.js';
import {JsxTransformer} from './JsxTransformer.js';
import {MemberVariableTransformer} from './MemberVariableTransformer.js';
Expand All @@ -27,6 +28,7 @@ import {validate as validateFreeVariables} from
* MultiTransformer that only transforms non ES6 features, such as:
* - annotations
* - types
* - async functions
*
* This is used to transform ES6+ code into pure ES6.
*/
Expand Down Expand Up @@ -68,6 +70,7 @@ export class PureES6Transformer extends MultiTransformer {
}
append(AnnotationsTransformer);
append(TypeTransformer);
append(AsyncToGeneratorTransformer);

if (options.modules === 'inline') {
append(InlineES6ModuleTransformer);
Expand Down
43 changes: 43 additions & 0 deletions src/runtime/modules/spawn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2016 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

export default function spawn(self, args, gen) {
return new Promise((resolve, reject) => {
function fulfill(v) {
try {
step(gen.next(v));
} catch (e) {
reject(e);
}
}

function rejected(v) {
try {
step(gen.throw(v));
} catch (e) {
reject(e);
}
}

function step(res) {
if (res.done) {
resolve(res.value);
} else {
Promise.resolve(res.value).then(fulfill, rejected);
}
}

step((gen = gen.apply(self, args)).next());
});
}
1 change: 1 addition & 0 deletions src/runtime/runtime-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ import './spread.js';
import './destructuring.js';
import './async.js';
import './generators.js';
import './spawn.js';
import './template.js';
import './jsx.js';
16 changes: 16 additions & 0 deletions src/runtime/spawn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2016 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import spawn from './modules/spawn.js'
$traceurRuntime.spawn = spawn;

0 comments on commit a1dc260

Please sign in to comment.