traceur
Version:
ES6 to ES5 compiler
705 lines (620 loc) • 21.6 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 {
ARRAY_LITERAL,
ARRAY_PATTERN,
ASSIGNMENT_ELEMENT,
BINDING_ELEMENT,
BINDING_IDENTIFIER,
BLOCK,
CALL_EXPRESSION,
COMPUTED_PROPERTY_NAME,
IDENTIFIER_EXPRESSION,
LITERAL_EXPRESSION,
MEMBER_EXPRESSION,
MEMBER_LOOKUP_EXPRESSION,
OBJECT_LITERAL,
OBJECT_PATTERN_FIELD,
OBJECT_PATTERN,
PAREN_EXPRESSION,
VARIABLE_DECLARATION_LIST,
VARIABLE_STATEMENT,
} from '../syntax/trees/ParseTreeType.js';
import {
AnonBlock,
AssignmentElement,
BindingElement,
Catch,
ExportDeclaration,
ForInStatement,
ForOfStatement,
ForOnStatement,
VariableDeclarationList,
VariableStatement,
} from '../syntax/trees/ParseTrees.js';
import ImportRuntimeTrait from './ImportRuntimeTrait.js';
import {TempVarTransformer} from './TempVarTransformer.js';
import {
EQUAL,
LET,
VAR
} from '../syntax/TokenType.js';
import bindingsInDestructuringPattern from '../semantics/bindingsInDestructuringPattern.js';
import {
createAssignmentExpression,
createBindingIdentifier,
createBlock,
createCommaExpression,
createExpressionStatement,
createFunctionBody,
createIdentifierExpression,
createMemberExpression,
createMemberLookupExpression,
createParenExpression,
createVariableDeclaration,
createVariableDeclarationList,
createVariableStatement
} from './ParseTreeFactory.js';
import {parseExpression} from './PlaceholderParser.js';
import {prependStatements} from './PrependStatements.js'
/**
* Collects assignments in the desugaring of a pattern.
*/
class Desugaring {
/**
* @param {ParseTree} rvalue
*/
constructor(rvalue) {
this.rvalue = rvalue;
this.expressions = [];
this.pendingExpressions = [];
}
createIterator(iterId) {
this.pendingExpressions.push(parseExpression
`${iterId} = ${this.rvalue}[Symbol.iterator]()`);
}
createInitializer(expression) {
if (this.pendingExpressions.length === 0) return expression;
let expressions = this.pendingExpressions;
this.pendingExpressions = [];
expressions.push(expression);
return createParenExpression(createCommaExpression(expressions));
}
skipHole(iterId) {
this.pendingExpressions.push(parseExpression `${iterId}.next()`);
}
}
/**
* Collects assignments as assignment expressions. This is the
* desugaring for assignment expressions.
*/
class AssignmentExpressionDesugaring extends Desugaring {
/**
* @param {ParseTree} rvalue
*/
constructor(rvalue) {
super(rvalue);
}
/**
* @param {AssignmentElement|IdentifierExpression} lvalue
* @param {ParseTree} rvalue
*/
assign(lvalue, rvalue) {
lvalue = lvalue instanceof AssignmentElement ? lvalue.assignment : lvalue;
rvalue = this.createInitializer(rvalue);
this.expressions.push(createAssignmentExpression(lvalue, rvalue));
}
createAssignmentExpression(tempId, rvalue) {
let expressions = this.expressions;
expressions.unshift(createAssignmentExpression(tempId, rvalue));
expressions.push(...this.pendingExpressions, tempId);
return createParenExpression(createCommaExpression(expressions));
}
}
/**
* Collects assignments as variable declarations. This is the
* desugaring for 'var', 'const' declarations.
*/
class VariableDeclarationDesugaring extends Desugaring {
/**
* @param {ParseTree} rvalue
*/
constructor(rvalue) {
super(rvalue);
}
/**
* @param {BindingElement|IdentifierExpression} lvalue
* @param {ParseTree} rvalue
*/
assign(lvalue, rvalue) {
let binding = lvalue instanceof BindingElement ?
lvalue.binding : createBindingIdentifier(lvalue);
rvalue = this.createInitializer(rvalue);
this.expressions.push(createVariableDeclaration(binding, rvalue));
}
get declarations() {
return this.expressions;
}
}
/**
* Desugars destructuring assignment.
*
* @see <a href="http://wiki.ecmascript.org/doku.php?id=harmony:destructuring#assignments">harmony:destructuring</a>
*/
export class DestructuringTransformer extends
ImportRuntimeTrait(TempVarTransformer) {
/**
* @param {UniqueIdentifierGenerator} identifierGenerator
*/
constructor(identifierGenerator, reporter, options) {
super(identifierGenerator, reporter, options);
this.parameterDeclarations = null;
}
/**
* @param {ArrayPattern} tree
* @return {ParseTree}
*/
transformArrayPattern(tree) {
// Patterns should be desugared by their parent nodes.
throw new Error('unreachable');
}
/**
* @param {ObjectPattern} tree
* @return {ParseTree}
*/
transformObjectPattern(tree) {
// Patterns should be desugared by their parent nodes.
throw new Error('unreachable');
}
/**
* Transforms:
* [a, ...b]] = x
* From an assignment expression into:
*
* ($__0 = x,
* a = ($__1 = $__0[Symbol.iterator](),
* ($__2 = $__1.next()).done ? void 0 : $__2.value),
* b = $traceurRuntime.iteratorToArray($__1),
* $__0);
*
* Nested patterns are desugared by recursive calls to transform.
*
* @param {BinaryExpression} tree
* @return {ParseTree}
*/
transformBinaryExpression(tree) {
this.pushTempScope();
let rv;
if (tree.operator.type === EQUAL && tree.left.isPattern()) {
rv = this.transformAny(this.desugarAssignment_(tree.left, tree.right));
} else {
rv = super.transformBinaryExpression(tree);
}
this.popTempScope();
return rv;
}
/**
* @param {ParseTree} lvalue
* @param {ParseTree} rvalue
* @return {ParseTree}
*/
desugarAssignment_(lvalue, rvalue) {
let tempId = createIdentifierExpression(this.addTempVar());
let desugaring = new AssignmentExpressionDesugaring(tempId);
this.desugarPattern_(desugaring, lvalue);
return desugaring.createAssignmentExpression(tempId, rvalue);
}
/**
* Transforms:
* [a, [b, c]] = x
* From a variable declaration list into:
* tmp = x, a = x[0], [b, c] = x[1]
*
* We do it this way (as opposed to a block with a declaration and
* initialization statements) so that we can translate const
* declarations, which must be initialised at declaration.
*
* Nested patterns are desugared by recursive calls to transform.
*
* @param {VariableDeclarationList} tree
* @return {ParseTree}
*/
transformVariableDeclarationList(tree) {
if (!hasDestructuring(tree)) {
// No lvalues to desugar.
return super.transformVariableDeclarationList(tree);
}
// Desugar one level of patterns.
let desugaredDeclarations = [];
tree.declarations.forEach((declaration) => {
if (declaration.lvalue.isPattern()) {
desugaredDeclarations.push(
...this.desugarVariableDeclaration_(declaration));
} else {
desugaredDeclarations.push(declaration);
}
});
// Desugar more.
let transformedTree = this.transformVariableDeclarationList(
createVariableDeclarationList(
tree.declarationType,
desugaredDeclarations));
return transformedTree;
}
transformForInStatement(tree) {
return this.transformForInOrOfOrOn_(tree,
super.transformForInStatement,
ForInStatement);
}
transformForOfStatement(tree) {
return this.transformForInOrOfOrOn_(tree,
super.transformForOfStatement,
ForOfStatement);
}
transformForOnStatement(tree) {
return this.transformForInOrOfOrOn_(tree,
super.transformForOnStatement,
ForOnStatement);
}
/**
* Transforms for-in and for-of loops.
* @param {ForInStatement|ForOfStatement} tree The for-in or for-of loop.
* @param {Function} superMethod The super method to call if no pattern is
* present.
* @param {Function} constr The constructor used to create the transformed
* tree.
* @return {ForInStatement|ForOfStatement} The transformed tree.
* @private
*/
transformForInOrOfOrOn_(tree, superMethod, constr) {
if (!tree.initializer.isPattern() &&
(tree.initializer.type !== VARIABLE_DECLARATION_LIST ||
!hasDestructuring(tree.initializer))) {
return superMethod.call(this, tree);
}
this.pushTempScope();
let declarationType, lvalue;
if (tree.initializer.isPattern()) {
declarationType = null;
lvalue = tree.initializer;
} else {
declarationType = tree.initializer.declarationType;
lvalue = tree.initializer.declarations[0].lvalue;
}
// for (var pattern in coll) {
//
// =>
//
// for (var $tmp in coll) {
// var pattern = $tmp;
//
// And when the initializer is an assignment expression.
//
// for (pattern in coll) {
//
// =>
//
// for (var $tmp in coll) {
// pattern = $tmp;
let statements = [];
let binding = this.desugarBinding_(lvalue, statements, declarationType);
let initializer = createVariableDeclarationList(VAR,
binding, null);
let collection = this.transformAny(tree.collection);
let body = this.transformAny(tree.body);
if (body.type === BLOCK)
statements.push(...body.statements);
else
statements.push(body);
body = createBlock(statements);
this.popTempScope();
return new constr(tree.location, initializer, collection, body);
}
transformAssignmentElement(tree) {
// Patterns should be desugared by their parent nodes.
throw new Error('unreachable');
}
transformBindingElement(tree) {
// If this has an initializer the default parameter transformer moves the
// pattern into the function body and it will be taken care of by the
// variable pass.
if (!tree.binding.isPattern() || tree.initializer)
return tree;
// function f(pattern) { }
//
// =>
//
// function f($tmp) {
// var pattern = $tmp;
// }
// We only get here for formal parameters. Variable declarations are handled
// further up in the transformer without calling transformBindingElement.
if (this.parameterDeclarations === null) {
this.parameterDeclarations = [];
this.pushTempScope(); // Popped in the function body.
}
let varName = this.getTempIdentifier();
let binding = createBindingIdentifier(varName);
let initializer = createIdentifierExpression(varName);
let decl = createVariableDeclaration(tree.binding, initializer);
this.parameterDeclarations.push(decl);
return new BindingElement(null, binding, null);
}
transformFunctionBody(tree) {
if (this.parameterDeclarations === null)
return super.transformFunctionBody(tree);
let list = createVariableDeclarationList(VAR, this.parameterDeclarations);
let statement = createVariableStatement(list);
let statements = prependStatements(tree.statements, statement);
let newBody = createFunctionBody(statements);
this.parameterDeclarations = null;
let result = super.transformFunctionBody(newBody);
this.popTempScope();
return result;
}
transformCatch(tree) {
if (!tree.binding.isPattern())
return super.transformCatch(tree);
// catch(pattern) {
//
// =>
//
// catch ($tmp) {
// let pattern = $tmp
let body = this.transformAny(tree.catchBody);
let statements = [];
let kind = this.options.blockBinding ? LET : VAR;
let binding = this.desugarBinding_(tree.binding, statements, kind);
statements.push(...body.statements);
return new Catch(tree.location, binding, createBlock(statements));
}
transformExportDeclaration(tree) {
if (tree.declaration.type === VARIABLE_STATEMENT &&
hasDestructuring(tree.declaration.declarations)) {
// Make sure we do not export the temp variable bindings.
const names = bindingsInDestructuringPattern(tree.declaration.declarations);
const declaration = this.transformAny(tree.declaration);
const statements = [];
const {declarations, declarationType} = declaration.declarations;
for (let i = 0; i < declarations.length; i++) {
const declaration = declarations[i];
let statement = new VariableStatement(declaration.location,
new VariableDeclarationList(declaration.location, declarationType, [declaration]));
if (names.has(declarations[i].lvalue.getStringValue())) {
statement = new ExportDeclaration(statement.location, statement, []);
}
statements.push(statement);
}
return new AnonBlock(null, statements);
}
return super.transformExportDeclaration(tree);
}
/**
* Helper for transformations that transforms a binding to a temp binding
* as well as a statement added into a block. For example, this is used by
* function, for-in/of and catch.
* @param {ParseTree} bindingTree The tree with the binding pattern.
* @param {Array} statements Array that we add the assignment/variable
* declaration to.
* @param {TokenType?} declarationType The kind of variable declaration to
* generate or null if an assignment expression is to be used.
* @return {BindingIdentifier} The binding tree.
*/
desugarBinding_(bindingTree, statements, declarationType) {
let varName = this.getTempIdentifier();
let binding = createBindingIdentifier(varName);
let idExpr = createIdentifierExpression(varName);
let desugaring;
if (declarationType === null)
desugaring = new AssignmentExpressionDesugaring(idExpr);
else
desugaring = new VariableDeclarationDesugaring(idExpr);
this.desugarPattern_(desugaring, bindingTree);
if (declarationType === null) {
statements.push(createExpressionStatement(
createCommaExpression(desugaring.expressions)));
} else {
statements.push(
createVariableStatement(
// Desugar more.
this.transformVariableDeclarationList(
createVariableDeclarationList(
declarationType,
desugaring.declarations))));
}
return binding;
}
/**
* @param {VariableDeclaration} tree
* @return {Array.<VariableDeclaration>}
*/
desugarVariableDeclaration_(tree) {
let tempRValueName = this.getTempIdentifier();
let tempRValueIdent = createIdentifierExpression(tempRValueName);
let desugaring;
let initializer;
// Don't use parens for these cases:
// - tree.initializer is assigned to a temporary.
// - tree.initializer normally doesn't need parens for member access.
// Don't use temporary if:
// - there is only one value to assign (and no initializer).
switch (tree.initializer.type) {
// Paren not necessary.
case ARRAY_LITERAL:
case CALL_EXPRESSION:
case IDENTIFIER_EXPRESSION:
case LITERAL_EXPRESSION:
case MEMBER_EXPRESSION:
case MEMBER_LOOKUP_EXPRESSION:
case OBJECT_LITERAL:
case PAREN_EXPRESSION:
initializer = tree.initializer;
}
// [1] Try first using a temporary (used later as the base rvalue).
desugaring = new VariableDeclarationDesugaring(tempRValueIdent);
desugaring.assign(desugaring.rvalue, tree.initializer);
let initializerFound = this.desugarPattern_(desugaring, tree.lvalue);
// [2] Was the temporary necessary? Then return.
if (initializerFound || desugaring.declarations.length > 2) {
return desugaring.declarations;
}
if (!initializer) {
initializer = createParenExpression(tree.initializer);
}
// [3] Redo everything without the temporary.
desugaring = new VariableDeclarationDesugaring(initializer);
this.desugarPattern_(desugaring, tree.lvalue);
return desugaring.declarations;
}
/**
* @param {Desugaring} desugaring
* @param {ParseTree} tree
* @return {boolean} True if any of the patterns have an initializer.
*/
desugarPattern_(desugaring, tree) {
let initializerFound = false;
let pattern;
switch (tree.type) {
case ARRAY_PATTERN: {
pattern = tree;
this.pushTempScope();
let iterId = createIdentifierExpression(this.addTempVar());
let iterObjectId = createIdentifierExpression(this.addTempVar());
desugaring.createIterator(iterId);
for (let i = 0; i < pattern.elements.length; i++) {
let lvalue = pattern.elements[i];
if (lvalue === null) {
// A skip, for example [a,,c]
desugaring.skipHole(iterId);
continue;
} else if (lvalue.isSpreadPatternElement()) {
// Rest of the array, for example [x, ...y] = [1, 2, 3]
let iteratorToArray = this.getRuntimeExpression('iteratorToArray');
desugaring.assign(
lvalue.lvalue,
parseExpression `${iteratorToArray}(${iterId})`);
} else {
if (lvalue.initializer) {
initializerFound = true;
}
desugaring.assign(
lvalue,
this.createConditionalIterExpression(iterObjectId, iterId,
lvalue.initializer));
}
}
this.popTempScope();
break;
}
case OBJECT_PATTERN: {
pattern = tree;
let elementHelper = (lvalue, initializer) => {
if (initializer)
initializerFound = true;
let lookup = this.createConditionalMemberExpression(desugaring.rvalue,
lvalue, initializer);
desugaring.assign(lvalue, lookup);
};
pattern.fields.forEach((field) => {
let lookup;
switch (field.type) {
case ASSIGNMENT_ELEMENT:
elementHelper(field.assignment, field.initializer);
break;
case BINDING_ELEMENT:
elementHelper(field.binding, field.initializer);
break;
case OBJECT_PATTERN_FIELD: {
if (field.element.initializer)
initializerFound = true;
let name = field.name;
lookup = this.createConditionalMemberExpression(desugaring.rvalue,
name, field.element.initializer);
desugaring.assign(field.element, lookup);
break;
}
default:
throw Error('unreachable');
}
});
break;
}
case PAREN_EXPRESSION:
return this.desugarPattern_(desugaring, tree.expression);
default:
throw new Error('unreachable');
}
// In case we have `var {} = expr` or `var [] = expr` we use a temp
// variable name so that the expression still gets executed.
//
// AssignmentExpressionDesugaring already works.
if (desugaring instanceof VariableDeclarationDesugaring &&
desugaring.declarations.length === 0) {
desugaring.assign(createBindingIdentifier(this.getTempIdentifier()),
desugaring.rvalue);
}
return initializerFound;
}
/**
* Creates something like:
*
* ($tmp = rvalue.ident) === undefined ? initializer : $tmp
*/
createConditionalMemberExpression(rvalue, name, initializer) {
if (name.type === COMPUTED_PROPERTY_NAME) {
return this.createConditionalMemberLookupExpression(rvalue,
name.expression, initializer);
}
let token;
switch (name.type) {
case BINDING_IDENTIFIER:
case IDENTIFIER_EXPRESSION:
token = name.identifierToken;
break;
default:
token = name.literalToken;
}
if (!initializer)
return createMemberExpression(rvalue, token);
let tempIdent = createIdentifierExpression(this.addTempVar());
return parseExpression `(${tempIdent} = ${rvalue}.${token}) === void 0 ?
${initializer} : ${tempIdent}`;
}
createConditionalMemberLookupExpression(rvalue, index, initializer) {
if (!initializer)
return createMemberLookupExpression(rvalue, index);
let tempIdent = createIdentifierExpression(this.addTempVar());
return parseExpression `(${tempIdent} = ${rvalue}[${index}]) === void 0 ?
${initializer} : ${tempIdent}`;
}
createConditionalIterExpression(iterObjectId, iterId, initializer) {
let expr = parseExpression `(${iterObjectId} =
${iterId}.next()).done ? void 0 : ${iterObjectId}.value`;
if (!initializer) {
return expr;
}
// TODO(arv): Simplify this expression?
let tempIdent = createIdentifierExpression(this.addTempVar());
return parseExpression `(${tempIdent} = ${expr}) === void 0 ?
${initializer} : ${tempIdent}`;
}
}
/**
* @param {VariableDeclarationList} tree
* @return {boolean}
*/
function hasDestructuring(tree) {
return tree.declarations.some(
(declaration) => declaration.lvalue.isPattern());
}