traceur
Version:
ES6 to ES5 compiler
345 lines (314 loc) • 10.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 {
ARGUMENT_LIST,
BLOCK,
EXPRESSION_STATEMENT,
FUNCTION_BODY,
IDENTIFIER_EXPRESSION
} from '../syntax/trees/ParseTreeType.js';
import {IdentifierToken} from '../syntax/IdentifierToken.js';
import {LiteralToken} from '../syntax/LiteralToken.js';
import {CollectingErrorReporter} from '../util/CollectingErrorReporter.js';
import {Options} from '../Options.js';
import {ParseTree} from '../syntax/trees/ParseTree.js';
import {ParseTreeTransformer} from './ParseTreeTransformer.js';
import {Parser} from '../syntax/Parser.js';
import {
LiteralExpression,
LiteralPropertyName,
TypeName
} from '../syntax/trees/ParseTrees.js';
import {SourceFile} from '../syntax/SourceFile.js';
import {IDENTIFIER} from '../syntax/TokenType.js';
import {
createArrayLiteral,
createBindingIdentifier,
createBlock,
createBooleanLiteral,
createCommaExpression,
createExpressionStatement,
createFunctionBody,
createIdentifierExpression,
createIdentifierToken,
createMemberExpression,
createNullLiteral,
createNumberLiteral,
createParenExpression,
createStringLiteral,
createVoid0
} from './ParseTreeFactory.js';
/**
* @fileoverview This file provides a few template string functions,
* |parseExpression|, |parseStatement|, etc, which parse a string with
* placeholders. The values in the placeholders can be JS values, ParseTrees or
* IdentifierTokens.
*
* For example:
*
* parseExpression`function() { return ${myTree}; }`
*
* returns a FunctionExpression tree that returns |myTree|.
*
* At the moment placeholders are allowed where a BindingIdentifier and
* IdentifierExpression are allowed.
*
* Beware that strings are treated as BindingIdentifiers in binding context but
* string literals in expression context. To work around that pass in an
* IdentifierToken.
*/
/**
* Sentinel used for |getValue_| to signal no value was found.
*/
const NOT_FOUND = {};
/**
* @param {function(Parser) : ParseTree} doParse Function that calls the correct
* parse method on the parser.
*/
function makeParseFunction(doParse) {
let cache = new Map();
return (sourceLiterals, ...values) => {
return parse(sourceLiterals, values, doParse, cache);
};
}
/**
* @param {Array.<string>} sourceLiterals
* @param {Array} values An array containing values or parse trees.
* @return {ParseTree}
*/
export let parseExpression = makeParseFunction((p) => p.parseExpression());
export let parseStatement = makeParseFunction((p) => p.parseStatement());
export let parseModule = makeParseFunction((p) => p.parseModule());
export let parseScript = makeParseFunction((p) => p.parseScript());
export let parseStatements = makeParseFunction((p) => p.parseStatements());
export let parsePropertyDefinition =
makeParseFunction((p) => p.parsePropertyDefinition());
function parse(sourceLiterals, values, doParse, cache) {
let tree = cache.get(sourceLiterals);
if (!tree) {
let source = insertPlaceholderIdentifiers(sourceLiterals);
let errorReporter = new CollectingErrorReporter();
let parser = getParser(source, errorReporter);
tree = doParse(parser);
if (errorReporter.hadError() || !tree || !parser.isAtEnd()) {
throw new Error(
`Internal error trying to parse:\n\n${source}\n\n${
errorReporter.errorsAsString()}`);
}
cache.set(sourceLiterals, tree);
}
if (!values.length)
return tree;
// We allow either a ParseTree or an Array as the result of doParse. An
// array is returned for parseStatements.
if (tree instanceof ParseTree)
return new PlaceholderTransformer(values).transformAny(tree);
return new PlaceholderTransformer(values).transformList(tree);
}
const PREFIX = '$__placeholder__';
/**
* @param {Array.<string>} sourceLiterals
* @return {string}
*/
function insertPlaceholderIdentifiers(sourceLiterals) {
let source = sourceLiterals[0];
for (let i = 1; i < sourceLiterals.length; i++) {
source += PREFIX + String(i - 1) + sourceLiterals[i];
}
return source;
}
let counter = 0;
function getParser(source, errorReporter) {
let file = new SourceFile(null, source);
let options = new Options();
// Enable internal code to always use all experimental features.
options.experimental = true;
return new Parser(file, errorReporter, options);
}
/**
* @param {*} value
* @return {ParseTree}
*/
function convertValueToExpression(value) {
if (value instanceof ParseTree)
return value;
if (value instanceof IdentifierToken)
return createIdentifierExpression(value);
if (value instanceof LiteralToken)
return new LiteralExpression(value.location, value);
if (Array.isArray(value)) {
if (value[0] instanceof ParseTree) {
if (value.length === 1)
return value[0];
if (value[0].isStatement())
return createBlock(value);
else
return createParenExpression(createCommaExpression(value));
}
return createArrayLiteral(value.map(convertValueToExpression));
}
if (value === null)
return createNullLiteral();
if (value === undefined)
return createVoid0();
switch (typeof value) {
case 'string':
return createStringLiteral(value);
case 'boolean':
return createBooleanLiteral(value);
case 'number':
return createNumberLiteral(value);
}
throw new Error('Not implemented');
}
function convertValueToIdentifierToken(value) {
if (value instanceof IdentifierToken)
return value;
return createIdentifierToken(value);
}
function convertValueToType(value) {
// We allow null here since it is common to have `var x : ${value}`.
if (value === null) return null;
if (value instanceof ParseTree) return value;
if (typeof value === 'string') {
return new TypeName(null, null, convertValueToIdentifierToken(value));
}
if (value instanceof IdentifierToken) {
return new TypeName(null, null, value);
}
throw new Error('Not implemented');
}
/**
* Transforms a ParseTree containing placeholders.
*/
export class PlaceholderTransformer extends ParseTreeTransformer {
/**
* @param {Array} values The values to replace the placeholders with.
*/
constructor(values) {
super();
this.values = values;
}
/**
* This gets called by the transformer when the index'th placeholder is
* going to be replaced.
* @param {number} index
* @return {ParseTree}
*/
getValueAt(index) {
return this.values[index];
}
/**
*
* @param {string} str
* @return {*} This returns the |NOT_FOUND| sentinel if the |str| does not
* represent a placeholder.
*/
getValue_(str) {
if (str.indexOf(PREFIX) !== 0)
return NOT_FOUND;
return this.getValueAt(Number(str.slice(PREFIX.length)));
}
transformIdentifierExpression(tree) {
let value = this.getValue_(tree.identifierToken.value);
if (value === NOT_FOUND)
return tree;
return convertValueToExpression(value);
}
transformBindingIdentifier(tree) {
let value = this.getValue_(tree.identifierToken.value);
if (value === NOT_FOUND)
return tree;
return createBindingIdentifier(value);
}
transformExpressionStatement(tree) {
if (tree.expression.type === IDENTIFIER_EXPRESSION) {
let transformedExpression =
this.transformIdentifierExpression(tree.expression);
if (transformedExpression === tree.expression)
return tree;
if (transformedExpression.isStatementListItem() ||
transformedExpression.type === FUNCTION_BODY) {
return transformedExpression;
}
return createExpressionStatement(transformedExpression);
}
return super.transformExpressionStatement(tree);
}
transformBlock(tree) {
if (tree.statements.length === 1 &&
tree.statements[0].type === EXPRESSION_STATEMENT) {
let transformedStatement =
this.transformExpressionStatement(tree.statements[0]);
if (transformedStatement === tree.statements[0])
return tree;
if (transformedStatement.type === BLOCK)
return transformedStatement;
}
return super.transformBlock(tree);
}
transformFunctionBody(tree) {
if (tree.statements.length === 1 &&
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)
return createFunctionBody(transformedStatement.statements);
}
return super.transformFunctionBody(tree);
}
transformMemberExpression(tree) {
let value = this.getValue_(tree.memberName.value);
if (value === NOT_FOUND)
return super.transformMemberExpression(tree);
let operand = this.transformAny(tree.operand);
return createMemberExpression(operand, value);
}
transformLiteralPropertyName(tree) {
if (tree.literalToken.type === IDENTIFIER) {
let value = this.getValue_(tree.literalToken.value);
if (value !== NOT_FOUND) {
return new LiteralPropertyName(null,
convertValueToIdentifierToken(value));
}
}
return super.transformLiteralPropertyName(tree);
}
transformArgumentList(tree) {
if (tree.args.length === 1 &&
tree.args[0].type === IDENTIFIER_EXPRESSION) {
let arg0 = this.transformAny(tree.args[0]);
if (arg0 === tree.args[0])
return tree;
if (arg0.type === ARGUMENT_LIST)
return arg0;
}
return super.transformArgumentList(tree);
}
transformTypeName(tree) {
let value = this.getValue_(tree.name.value);
if (value === NOT_FOUND)
return super.transformTypeName(tree);
let moduleName = this.transformAny(tree.moduleName);
if (moduleName !== null) {
return new TypeName(null, moduleName,
convertValueToIdentifierToken(value));
}
return convertValueToType(value);
}
}