refactor(transpiler): split the monolithic dart transformer

fixes #24

The new architecture conforms with the Traceur architecture.
This commit is contained in:
Victor Berchet 2014-09-30 16:14:22 +02:00
parent 92375c0281
commit 64d3cc68f0
7 changed files with 215 additions and 127 deletions

View File

@ -0,0 +1,68 @@
import {ParseTreeTransformer} from 'traceur/src/codegeneration/ParseTreeTransformer';
import {
PROPERTY_METHOD_ASSIGNMENT,
MEMBER_EXPRESSION,
THIS_EXPRESSION,
BINARY_EXPRESSION
} from 'traceur/src/syntax/trees/ParseTreeType';
import {CONSTRUCTOR} from 'traceur/src/syntax/PredefinedName';
import {propName} from 'traceur/src/staticsemantics/PropName';
import {ClassFieldParseTree} from '../ast/class_field';
/**
* Transforms class declaration:
* - rename constructor (name of the class - default Dart constructor)
*
*/
export class ClassTransformer extends ParseTreeTransformer {
/**
* @param {ClassDeclaration} tree
* @returns {ParseTree}
*/
transformClassDeclaration(tree) {
var className = tree.name.identifierToken.toString();
var argumentTypesMap = {};
var fields = [];
tree.elements.forEach(function(elementTree) {
if (elementTree.type === PROPERTY_METHOD_ASSIGNMENT &&
!elementTree.isStatic &&
propName(elementTree) === CONSTRUCTOR) {
// Store constructor argument types,
// so that we can use them to set the types of simple-assigned fields.
elementTree.parameterList.parameters.forEach(function(p) {
var binding = p.parameter.binding;
if (binding.identifierToken) {
argumentTypesMap[binding.identifierToken.value] = p.typeAnnotation;
}
});
// Rename "constructor" to the class name.
elementTree.name.literalToken.value = className;
// Collect all fields, defined in the constructor.
elementTree.body.statements.forEach(function(statement) {
if (statement.expression.type === BINARY_EXPRESSION &&
statement.expression.operator.type === '=' &&
statement.expression.left.type === MEMBER_EXPRESSION &&
statement.expression.left.operand.type === THIS_EXPRESSION) {
var typeAnnotation = argumentTypesMap[statement.expression.left.memberName.value] || null;
fields.push(new ClassFieldParseTree(tree.location, statement.expression.left.memberName, typeAnnotation));
}
});
}
});
// Add the field definitions to the beginning of the class.
tree.elements = fields.concat(tree.elements);
return super(tree);
};
}

View File

@ -0,0 +1,28 @@
import {MultiTransformer} from 'traceur/src/codegeneration/MultiTransformer';
import {UniqueIdentifierGenerator} from 'traceur/src/codegeneration/UniqueIdentifierGenerator';
import {options} from 'traceur/src/Options';
import {ClassTransformer} from './ClassTransformer';
import {InstanceOfTransformer} from './InstanceOfTransformer';
import {MultiVarTransformer} from './MultiVarTransformer';
import {StrictEqualityTransformer} from './StrictEqualityTransformer';
/**
* Transforms ES6 + annotations to Dart code.
*/
export class DartTransformer extends MultiTransformer {
constructor(reporter, idGenerator = new UniqueIdentifierGenerator()) {
super(reporter, options.validate);
var append = (transformer) => {
this.append((tree) => {
return new transformer(idGenerator, reporter).transformAny(tree);
});
};
append(MultiVarTransformer);
append(InstanceOfTransformer);
append(StrictEqualityTransformer);
append(ClassTransformer);
}
}

View File

@ -0,0 +1,25 @@
import {INSTANCEOF} from 'traceur/src/syntax/TokenType';
import {ParseTreeTransformer} from 'traceur/src/codegeneration/ParseTreeTransformer';
/**
* Transforms `a instanceof b` to `a is b`,
*/
export class InstanceOfTransformer extends ParseTreeTransformer {
/**
* @param {BinaryExpression} tree
* @return {ParseTree}
*/
transformBinaryExpression(tree) {
tree.left = this.transformAny(tree.left);
tree.right = this.transformAny(tree.right);
if (tree.operator.type === 'instanceof') {
// TODO(vojta): do this in a cleaner way.
tree.operator.type = 'is';
return tree;
}
return tree;
}
}

View File

@ -0,0 +1,46 @@
import {VariableStatement, VariableDeclarationList} from 'traceur/src/syntax/trees/ParseTrees';
import {ParseTreeTransformer} from 'traceur/src/codegeneration/ParseTreeTransformer';
/**
* Transforms `var a, b;` to `var a; var b;`
*/
export class MultiVarTransformer extends ParseTreeTransformer {
// Individual item transformer can return an array of items.
// This is used in `transformVariableStatement`.
// Otherwise this is copy/pasted from `ParseTreeTransformer`.
transformList(list) {
var transformedList = [];
var transformedItem = null;
for (var i = 0, ii = list.length; i < ii; i++) {
transformedItem = this.transformAny(list[i]);
if (Array.isArray(transformedItem)) {
transformedList = transformedList.concat(transformedItem);
} else {
transformedList.push(transformedItem);
}
}
return transformedList;
}
/**
* @param {VariableStatement} tree
* @returns {ParseTree}
*/
transformVariableStatement(tree) {
var declarations = tree.declarations.declarations;
if (declarations.length === 1 || declarations.length === 0) {
return tree;
}
// Multiple var declaration, we will split it into multiple statements.
// TODO(vojta): We can leave the multi-definition as long as they are all the same type/untyped.
return declarations.map(function(declaration) {
return new VariableStatement(tree.location, new VariableDeclarationList(tree.location,
tree.declarations.declarationType, [declaration]));
});
}
}

View File

@ -0,0 +1,42 @@
import {
createCallExpression,
createIdentifierExpression,
createArgumentList} from 'traceur/src/codegeneration/ParseTreeFactory';
import {
EQUAL_EQUAL_EQUAL,
NOT_EQUAL_EQUAL
} from 'traceur/src/syntax/TokenType';
import {ParseTreeTransformer} from 'traceur/src/codegeneration/ParseTreeTransformer';
/**
* Transforms:
* - `a === b` to `indentical(a, b)`,
* - `a !== b` to `!identical(a, b)`
*/
export class StrictEqualityTransformer extends ParseTreeTransformer {
/**
* @param {BinaryExpression} tree
* @return {ParseTree}
*/
transformBinaryExpression(tree) {
tree.left = this.transformAny(tree.left);
tree.right = this.transformAny(tree.right);
if (tree.operator.type === EQUAL_EQUAL_EQUAL) {
// `a === b` -> `identical(a, b)`
return createCallExpression(createIdentifierExpression('identical'),
createArgumentList([tree.left, tree.right]));
}
if (tree.operator.type === NOT_EQUAL_EQUAL) {
// `a !== b` -> `!identical(a, b)`
// TODO(vojta): do this in a cleaner way.
return createCallExpression(createIdentifierExpression('!identical'),
createArgumentList([tree.left, tree.right]));
}
return tree;
}
}

View File

@ -1,5 +1,5 @@
import {Compiler as TraceurCompiler} from 'traceur/src/Compiler'; import {Compiler as TraceurCompiler} from 'traceur/src/Compiler';
import {ClassTransformer} from './dart_class_transformer'; import {DartTransformer} from './codegeneration/DartTransformer';
import {DartTreeWriter} from './dart_writer'; import {DartTreeWriter} from './dart_writer';
import {CollectingErrorReporter} from 'traceur/src/util/CollectingErrorReporter'; import {CollectingErrorReporter} from 'traceur/src/util/CollectingErrorReporter';
import {Parser} from './parser'; import {Parser} from './parser';
@ -16,8 +16,11 @@ export class Compiler extends TraceurCompiler {
transform(tree, moduleName = undefined) { transform(tree, moduleName = undefined) {
if (this.options_.outputLanguage.toLowerCase() === 'dart') { if (this.options_.outputLanguage.toLowerCase() === 'dart') {
var transformer = new ClassTransformer(); var errorReporter = new CollectingErrorReporter();
return transformer.transformAny(tree); var transformer = new DartTransformer(errorReporter);
var transformedTree = transformer.transform(tree);
this.throwIfErrors(errorReporter);
return transformedTree;
} else { } else {
return super(tree, moduleName); return super(tree, moduleName);
} }

View File

@ -1,124 +0,0 @@
import {ParseTreeTransformer} from 'traceur/src/codegeneration/ParseTreeTransformer';
import {createVariableStatement, createCallExpression, createIdentifierExpression, createArgumentList} from 'traceur/src/codegeneration/ParseTreeFactory';
// var token = traceur.syntax.TokenType;
// var CONSTRUCTOR = token.CONSTRUCTOR;
import {PROPERTY_METHOD_ASSIGNMENT, MEMBER_EXPRESSION, THIS_EXPRESSION, BINARY_EXPRESSION} from 'traceur/src/syntax/trees/ParseTreeType';
import {EQUAL_EQUAL_EQUAL, NOT_EQUAL_EQUAL} from 'traceur/src/syntax/TokenType';
import {CONSTRUCTOR} from 'traceur/src/syntax/PredefinedName';
import {VariableStatement, VariableDeclarationList} from 'traceur/src/syntax/trees/ParseTrees';
import {propName} from 'traceur/src/staticsemantics/PropName';
import {ClassFieldParseTree} from './ast/class_field';
// - rename constructor (name of the class - default Dart constructor)
export class ClassTransformer extends ParseTreeTransformer {
// Transform multi-var declarations, into multiple statements:
// var x, y;
// ==>
// var x;
// var y;
// TODO(vojta): move this into a separate transformer.
// Individual item transformer can return an array of items.
// This is used in `transformVariableStatement`.
// Otherwise this is copy/pasted from `ParseTreeTransformer`.
transformList(list) {
var transformedList = [];
var transformedItem = null;
for (var i = 0, ii = list.length; i < ii; i++) {
transformedItem = this.transformAny(list[i]);
if (Array.isArray(transformedItem)) {
transformedList = transformedList.concat(transformedItem);
} else {
transformedList.push(transformedItem);
}
}
return transformedList;
}
transformVariableStatement(tree) {
var declarations = tree.declarations.declarations;
if (declarations.length === 1 || declarations.length === 0) {
return tree;
}
// Multiple var declaration, we will split it into multiple statements.
// TODO(vojta): We can leave the multi-definition as long as they are all the same type/untyped.
return declarations.map(function(declaration) {
return new VariableStatement(tree.location, new VariableDeclarationList(tree.location,
tree.declarations.declarationType, [declaration]));
});
}
// Transform triple equals into identical() call.
// TODO(vojta): move to a separate transformer
transformBinaryExpression(tree) {
tree.left = this.transformAny(tree.left);
tree.right = this.transformAny(tree.right);
if (tree.operator.type === 'instanceof') {
// a instanceof b -> a is b
// TODO(vojta): do this in a cleaner way.
tree.operator.type = 'is';
return tree;
} else if (tree.operator.type === EQUAL_EQUAL_EQUAL) {
// a === b -> identical(a, b)
return createCallExpression(createIdentifierExpression('identical'), createArgumentList([tree.left, tree.right]));
} else if (tree.operator.type === NOT_EQUAL_EQUAL) {
// a !== b -> !identical(a, b)
// TODO(vojta): do this in a cleaner way.
return createCallExpression(createIdentifierExpression('!identical'), createArgumentList([tree.left, tree.right]));
} else {
return tree;
}
};
transformClassDeclaration(tree) {
var className = tree.name.identifierToken.toString();
var argumentTypesMap = {};
var fields = [];
tree.elements.forEach(function(elementTree) {
if (elementTree.type === PROPERTY_METHOD_ASSIGNMENT &&
!elementTree.isStatic &&
propName(elementTree) === CONSTRUCTOR) {
// Store constructor argument types,
// so that we can use them to set the types of simple-assigned fields.
elementTree.parameterList.parameters.forEach(function(p) {
var binding = p.parameter.binding;
if (binding.identifierToken) {
argumentTypesMap[binding.identifierToken.value] = p.typeAnnotation;
}
});
// Rename "constructor" to the class name.
elementTree.name.literalToken.value = className;
// Collect all fields, defined in the constructor.
elementTree.body.statements.forEach(function(statement) {
if (statement.expression.type === BINARY_EXPRESSION &&
statement.expression.operator.type === '=' &&
statement.expression.left.type === MEMBER_EXPRESSION &&
statement.expression.left.operand.type === THIS_EXPRESSION) {
var typeAnnotation = argumentTypesMap[statement.expression.left.memberName.value] || null;
fields.push(new ClassFieldParseTree(tree.location, statement.expression.left.memberName, typeAnnotation));
}
});
}
});
// Add the field definitions to the begining of the class.
tree.elements = fields.concat(tree.elements);
return super(tree);
};
}