traceur
Version:
ES6 to ES5 compiler
317 lines (279 loc) • 9.67 kB
JavaScript
// Copyright 2012 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 {CPSTransformer} from './CPSTransformer.js';
import {
BINARY_EXPRESSION,
YIELD_EXPRESSION
} from '../../syntax/trees/ParseTreeType.js';
import {
BinaryExpression,
ExpressionStatement
} from '../../syntax/trees/ParseTrees.js'
import {FindInFunctionScope} from '../FindInFunctionScope.js'
import {ReturnState} from './ReturnState.js';
import ImportRuntimeTrait from '../ImportRuntimeTrait.js';
import {YieldState} from './YieldState.js';
import {
createIdentifierExpression as id,
createMemberExpression,
createUndefinedExpression,
} from '../ParseTreeFactory.js';
import {
parseExpression,
parseStatement,
parseStatements
} from '../PlaceholderParser.js';
/**
* @param {ParseTree} tree Expression tree
* @return {boolean}
*/
function isYieldAssign(tree) {
return tree.type === BINARY_EXPRESSION &&
tree.operator.isAssignmentOperator() &&
tree.right.type === YIELD_EXPRESSION &&
tree.left.isLeftHandSideExpression();
}
class YieldFinder extends FindInFunctionScope {
visitYieldExpression(tree) {
this.found = true;
}
}
function scopeContainsYield(tree) {
let finder = new YieldFinder();
finder.visitAny(tree);
return finder.found;
}
/**
* Desugars generator function bodies. Generator function bodies contain
* 'yield' statements.
*
* At the top level the state machine is translated into this source code:
*
* {
* machine variables
* return $traceurRuntime.createGeneratorInstance(machineFunction);
* }
*/
export class GeneratorTransformer extends ImportRuntimeTrait(CPSTransformer) {
constructor(identifierGenerator, reporter, options) {
super(identifierGenerator, reporter, options);
this.shouldAppendThrowCloseState_ = true;
}
expressionNeedsStateMachine(tree) {
if (tree === null)
return false;
return scopeContainsYield(tree);
}
/**
* Simple form yield expressions (direct children of an ExpressionStatement)
* are translated into a state machine with a single state.
* @param {YieldExpression} tree
* @return {ParseTree}
* @private
*/
transformYieldExpression_(tree) {
let expression, machine;
if (this.expressionNeedsStateMachine(tree.expression)) {
({expression, machine} = this.expressionToStateMachine(tree.expression));
} else {
expression = this.transformAny(tree.expression);
}
if (tree.isYieldFor)
return this.transformYieldForExpression_(expression, machine);
let startState = this.allocateState();
let fallThroughState = this.allocateState();
let yieldMachine = this.stateToStateMachine_(
new YieldState(startState, fallThroughState, expression),
fallThroughState);
if (machine)
yieldMachine = machine.append(yieldMachine);
// The yield expression we generated for the yield-for expression should not
// be followed by the ThrowCloseState since the inner iterator need to
// handle the throw case.
if (this.shouldAppendThrowCloseState_)
yieldMachine = yieldMachine.append(this.createThrowCloseState_());
return yieldMachine;
}
transformYieldForExpression_(expression, machine = undefined) {
let gName = this.getTempIdentifier();
this.addMachineVariable(gName);
let g = id(gName);
let nextName = this.getTempIdentifier();
this.addMachineVariable(nextName);
let next = id(nextName);
// http://wiki.ecmascript.org/doku.php?id=harmony:generators
// Updated on es-discuss
//
// The expression yield* <<expr>> is equivalent to:
//
// let (g = EXPR) {
// let received = void 0, send = true;
// for (;;) {
// let next = send ? g.next(received) : g.throw(received);
// if (next.done)
// break;
// try {
// received = yield next.value; // ***
// send = true;
// } catch (e) {
// received = e;
// send = false;
// }
// }
// next.value;
// }
let statements = parseStatements `
${g} = $ctx.wrapYieldStar(${expression}[Symbol.iterator]());
// received = void 0;
$ctx.sent = void 0;
// send = true; // roughly equivalent
$ctx.action = 'next';
for (;;) {
${next} = ${g}[$ctx.action]($ctx.sentIgnoreThrow);
if (${next}.done) {
$ctx.sent = ${next}.value;
break;
}
yield ${next}.value;
}`;
// The yield above should not be treated the same way as a normal yield.
// See comment in transformYieldExpression_.
let shouldAppendThrowCloseState = this.shouldAppendThrowCloseState_;
this.shouldAppendThrowCloseState_ = false;
statements = this.transformList(statements);
let yieldMachine = this.transformStatementList_(statements);
this.shouldAppendThrowCloseState_ = shouldAppendThrowCloseState;
if (machine)
yieldMachine = machine.append(yieldMachine);
// TODO(arv): Another option is to build up the statemachine for this here
// instead of builing the code and transforming the code into a state
// machine.
return yieldMachine;
}
/**
* @param {YieldExpression} tree
* @return {ParseTree}
*/
transformYieldExpression(tree) {
this.reporter.reportError(tree.location,
'Only \'a = yield b\' and \'var a = yield b\' currently supported.');
return tree;
}
/**
* @param {BinaryExpression} tree
*/
transformYieldAssign_(tree) {
let shouldAppendThrowCloseState = this.shouldAppendThrowCloseState_;
this.shouldAppendThrowCloseState_ = false;
let machine = this.transformYieldExpression_(tree.right);
let left = this.transformAny(tree.left);
let sentExpression = tree.right.isYieldFor ?
parseExpression `$ctx.sentIgnoreThrow` :
parseExpression `$ctx.sent`;
let statement = new ExpressionStatement(
tree.location,
new BinaryExpression(
tree.location,
left,
tree.operator,
sentExpression));
let assignMachine = this.statementToStateMachine_(statement);
this.shouldAppendThrowCloseState_ = shouldAppendThrowCloseState;
return machine.append(assignMachine);
}
createThrowCloseState_() {
return this.statementToStateMachine_(parseStatement `$ctx.maybeThrow()`);
}
/**
* @param {ExpressionStatement} tree
* @return {ParseTree}
*/
transformExpressionStatement(tree) {
let expression = tree.expression;
if (expression.type === YIELD_EXPRESSION)
return this.transformYieldExpression_(expression);
if (isYieldAssign(expression))
return this.transformYieldAssign_(expression);
if (this.expressionNeedsStateMachine(expression)) {
return this.expressionToStateMachine(expression).machine;
}
return super.transformExpressionStatement(tree);
}
/**
* @param {AwaitStatement} tree
* @return {ParseTree}
*/
transformAwaitStatement(tree) {
// TODO(arv): This should be handled in the parser... change to throw.
this.reporter.reportError(tree.location,
'Generator function may not have an await statement.');
return tree;
}
/**
* @param {ReturnStatement} tree
* @return {ParseTree}
*/
transformReturnStatement(tree) {
let expression, machine;
if (this.expressionNeedsStateMachine(tree.expression))
({expression, machine} = this.expressionToStateMachine(tree.expression));
else
expression = tree.expression;
let startState = this.allocateState();
let fallThroughState = this.allocateState();
let returnMachine = this.stateToStateMachine_(
new ReturnState(
startState,
fallThroughState,
this.transformAny(expression)),
fallThroughState);
if (machine)
return machine.append(returnMachine);
return returnMachine
}
/**
* Transform a generator function body - removing yield statements.
* The transformation is in two stages. First the statements are converted
* into a single state machine by merging state machines via a bottom up
* traversal.
*
* Then the final state machine is converted into the following code:
*
* {
* machine variables
* return $traceurRuntime.createGeneratorInstance(machineFunction);
* }
*
* @param {FunctionBody} tree
* @param {IdentifierExpression} name
* @return {FunctionBody}
*/
transformGeneratorBody(tree, name) {
let createGeneratorInstance =
this.getRuntimeExpression('createGeneratorInstance');
return this.transformCpsFunctionBody(tree, createGeneratorInstance, name);
}
/**
* @param {UniqueIdentifierGenerator} identifierGenerator
* @param {ErrorReporter} reporter
* @param {Block} body
* @param {IdentifierExpression} name
* @return {Block}
*/
static transformGeneratorBody(identifierGenerator, reporter, options, body,
name) {
return new GeneratorTransformer(identifierGenerator, reporter, options).
transformGeneratorBody(body, name);
}
};