Add basic class lowering
This change introduces basic class lowering into MuPack/MuIL. There are a handful of things not yet supported (like extends/implements and properties), however, simple methods and variables are supported.
This commit is contained in:
parent
fc1899e1ee
commit
82657c7cdb
|
@ -91,6 +91,16 @@ interface VariableDeclaration {
|
|||
initializer?: ast.Expression; // an optional initialization expression.
|
||||
}
|
||||
|
||||
// A function declaration isn't yet known to be a module or class method, and so it just contains the subset that is
|
||||
// common between them. This facilitates code reuse in the translation passes.
|
||||
interface FunctionDeclaration {
|
||||
node: ts.Node;
|
||||
name: ast.Identifier;
|
||||
parameters: ast.LocalVariable[];
|
||||
body?: ast.Block;
|
||||
returnType?: symbols.TypeToken;
|
||||
}
|
||||
|
||||
// A transpiler is responsible for transforming TypeScript program artifacts into MuPack/MuIL AST forms.
|
||||
export class Transpiler {
|
||||
private meta: pack.Metadata; // the package's metadata.
|
||||
|
@ -416,7 +426,40 @@ export class Transpiler {
|
|||
/** Declaration statements **/
|
||||
|
||||
private transformClassDeclaration(node: ts.ClassDeclaration, access: symbols.Accessibility): ast.Class {
|
||||
return contract.fail("NYI");
|
||||
// TODO(joe): generics.
|
||||
// TODO(joe): decorators.
|
||||
// TODO(joe): extends/implements.
|
||||
|
||||
// First transform the name into an identifier. In the absence of a name, we will proceed under the assumption
|
||||
// that it is the default export. This should be verified later on.
|
||||
let name: ast.Identifier;
|
||||
if (node.name) {
|
||||
name = this.transformIdentifier(node.name);
|
||||
}
|
||||
else {
|
||||
name = {
|
||||
kind: ast.identifierKind,
|
||||
ident: defaultExport,
|
||||
};
|
||||
}
|
||||
|
||||
// Transform all non-semicolon members for this declaration.
|
||||
let members: ast.ClassMembers = {};
|
||||
for (let member of node.members) {
|
||||
if (member.kind !== ts.SyntaxKind.SemicolonClassElement) {
|
||||
let result: ast.ClassMember = this.transformClassElement(member);
|
||||
members[result.name.ident] = result;
|
||||
}
|
||||
}
|
||||
|
||||
let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node);
|
||||
return this.copyLocation(node, {
|
||||
kind: ast.classKind,
|
||||
name: name,
|
||||
access: access,
|
||||
members: members,
|
||||
abstract: !!(mods & ts.ModifierFlags.Abstract),
|
||||
});
|
||||
}
|
||||
|
||||
private transformDeclarationName(node: ts.DeclarationName): ast.Expression {
|
||||
|
@ -443,10 +486,7 @@ export class Transpiler {
|
|||
}
|
||||
}
|
||||
|
||||
private transformFunctionDeclaration<T extends ast.Function>(
|
||||
node: ts.FunctionDeclaration, kind: ast.NodeKind, access: symbols.Accessibility): ast.Function {
|
||||
contract.requires(!!node.body, "node", "Expected a function body");
|
||||
|
||||
private transformFunctionLikeDeclaration(node: ts.FunctionLikeDeclaration): FunctionDeclaration {
|
||||
// Ensure we are dealing with the supported subset of functions.
|
||||
// TODO: turn these into real errors.
|
||||
if (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Async) {
|
||||
|
@ -460,17 +500,49 @@ export class Transpiler {
|
|||
// that it is the default export. This should be verified later on.
|
||||
let name: ast.Identifier;
|
||||
if (node.name) {
|
||||
name = this.transformIdentifier(node.name);
|
||||
name = this.transformPropertyName(node.name);
|
||||
}
|
||||
else {
|
||||
// Create a default identifier name.
|
||||
let ident: string;
|
||||
if (node.kind === ts.SyntaxKind.Constructor) {
|
||||
// Constructors have a special name.
|
||||
ident = symbols.specialFunctionConstructor;
|
||||
}
|
||||
else {
|
||||
// All others are assumed to be default exports.
|
||||
ident = defaultExport;
|
||||
}
|
||||
name = {
|
||||
kind: ast.identifierKind,
|
||||
ident: defaultExport,
|
||||
ident: ident,
|
||||
};
|
||||
}
|
||||
|
||||
// Now visit the body; it can either be a block or a free-standing expression.
|
||||
let body: ast.Block = this.transformBlock(<ts.Block>node.body!);
|
||||
let body: ast.Block | undefined;
|
||||
if (node.body) {
|
||||
switch (node.body.kind) {
|
||||
case ts.SyntaxKind.Block:
|
||||
body = this.transformBlock(<ts.Block>node.body);
|
||||
break;
|
||||
default:
|
||||
// Translate a body of <expr> into
|
||||
// {
|
||||
// return <expr>;
|
||||
// }
|
||||
body = {
|
||||
kind: ast.blockKind,
|
||||
statements: [
|
||||
<ast.ReturnStatement>{
|
||||
kind: ast.returnStatementKind,
|
||||
expression: this.transformExpression(<ts.Expression>node.body),
|
||||
},
|
||||
],
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Next transform the parameter variables into locals.
|
||||
let parameters: VariableDeclaration[] = node.parameters.map(
|
||||
|
@ -478,21 +550,33 @@ export class Transpiler {
|
|||
|
||||
// If there are any initializers, make sure to prepend them (in order) to the body block.
|
||||
for (let parameter of parameters) {
|
||||
if (parameter.initializer) {
|
||||
if (parameter.initializer && body) {
|
||||
body.statements = [ this.makeVariableInitializer(parameter) ].concat(body.statements);
|
||||
}
|
||||
}
|
||||
|
||||
return <T><any>{
|
||||
kind: kind,
|
||||
return {
|
||||
node: node,
|
||||
name: name,
|
||||
access: access,
|
||||
parameters: parameters.map((p: VariableDeclaration) => p.local),
|
||||
body: body,
|
||||
returnType: "TODO",
|
||||
};
|
||||
}
|
||||
|
||||
private transformFunctionDeclaration<TFunction extends ast.Function>(
|
||||
node: ts.FunctionDeclaration, kind: ast.NodeKind, access: symbols.Accessibility): TFunction {
|
||||
let decl: FunctionDeclaration = this.transformFunctionLikeDeclaration(node);
|
||||
return this.copyLocation(node, <TFunction><any>{
|
||||
kind: kind,
|
||||
name: decl.name,
|
||||
access: access,
|
||||
parameters: decl.parameters,
|
||||
body: decl.body,
|
||||
returnType: decl.returnType,
|
||||
});
|
||||
}
|
||||
|
||||
private transformInterfaceDeclaration(node: ts.InterfaceDeclaration, access: symbols.Accessibility): ast.Class {
|
||||
return contract.fail("NYI");
|
||||
}
|
||||
|
@ -659,7 +743,34 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformClassElementFunctionLike(node: ts.FunctionLikeDeclaration): ast.Definition {
|
||||
return contract.fail("NYI");
|
||||
// Get/Set accessors aren't yet supported.
|
||||
contract.assert(node.kind !== ts.SyntaxKind.GetAccessor, "GetAccessor NYI");
|
||||
contract.assert(node.kind !== ts.SyntaxKind.SetAccessor, "SetAccessor NYI");
|
||||
|
||||
let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node);
|
||||
let decl: FunctionDeclaration = this.transformFunctionLikeDeclaration(node);
|
||||
let access: symbols.ClassMemberAccessibility;
|
||||
if (!!(mods & ts.ModifierFlags.Private)) {
|
||||
access = symbols.privateAccessibility;
|
||||
}
|
||||
else if (!!(mods & ts.ModifierFlags.Protected)) {
|
||||
access = symbols.protectedAccessibility;
|
||||
}
|
||||
else {
|
||||
// All members are public by default in ECMA/TypeScript.
|
||||
access = symbols.publicAccessibility;
|
||||
}
|
||||
|
||||
return this.copyLocation(node, {
|
||||
kind: ast.classMethodKind,
|
||||
name: decl.name,
|
||||
access: access,
|
||||
parameters: decl.parameters,
|
||||
body: decl.body,
|
||||
returnType: decl.returnType,
|
||||
static: !!(mods & ts.ModifierFlags.Static),
|
||||
abstract: !!(mods & ts.ModifierFlags.Abstract),
|
||||
});
|
||||
}
|
||||
|
||||
private transformClassElementProperty(node: ts.PropertyDeclaration): ast.ClassProperty {
|
||||
|
@ -1156,8 +1267,13 @@ export class Transpiler {
|
|||
return contract.fail("NYI");
|
||||
}
|
||||
|
||||
private transformPropertyName(node: ts.PropertyName): ast.Expression {
|
||||
return contract.fail("NYI");
|
||||
private transformPropertyName(node: ts.PropertyName): ast.Identifier {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.Identifier:
|
||||
return this.transformIdentifier(<ts.Identifier>node);
|
||||
default:
|
||||
return contract.fail("Property names other than identifiers not yet supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,13 @@ import {asyncTest} from "../util";
|
|||
let testCases: string[] = [
|
||||
"empty",
|
||||
|
||||
"modules/var_1",
|
||||
"modules/var_exp_1",
|
||||
"modules/func_1",
|
||||
"modules/func_exp_1",
|
||||
"modules/func_exp_def_1",
|
||||
"modules/var_1",
|
||||
"modules/var_exp_1",
|
||||
"modules/class_1",
|
||||
"modules/class_exp_1",
|
||||
];
|
||||
|
||||
describe("outputs", () => {
|
||||
|
|
4
tools/mujs/tests/output/modules/class_1/Mu.json
Normal file
4
tools/mujs/tests/output/modules/class_1/Mu.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "class_1"
|
||||
}
|
||||
|
57
tools/mujs/tests/output/modules/class_1/Mu.out.json
Normal file
57
tools/mujs/tests/output/modules/class_1/Mu.out.json
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "class_1",
|
||||
"modules": {
|
||||
"index": {
|
||||
"kind": "Module",
|
||||
"name": {
|
||||
"kind": "Identifier",
|
||||
"ident": "index"
|
||||
},
|
||||
"members": {
|
||||
"C": {
|
||||
"kind": "Class",
|
||||
"name": {
|
||||
"kind": "Identifier",
|
||||
"ident": "C",
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 6
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 7
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "private",
|
||||
"members": {},
|
||||
"abstract": false,
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
tools/mujs/tests/output/modules/class_1/index.ts
Normal file
3
tools/mujs/tests/output/modules/class_1/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
class C {
|
||||
}
|
||||
|
6
tools/mujs/tests/output/modules/class_1/tsconfig.json
Normal file
6
tools/mujs/tests/output/modules/class_1/tsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"files": [
|
||||
"index.ts"
|
||||
]
|
||||
}
|
||||
|
4
tools/mujs/tests/output/modules/class_exp_1/Mu.json
Normal file
4
tools/mujs/tests/output/modules/class_exp_1/Mu.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "class_exp_1"
|
||||
}
|
||||
|
57
tools/mujs/tests/output/modules/class_exp_1/Mu.out.json
Normal file
57
tools/mujs/tests/output/modules/class_exp_1/Mu.out.json
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "class_exp_1",
|
||||
"modules": {
|
||||
"index": {
|
||||
"kind": "Module",
|
||||
"name": {
|
||||
"kind": "Identifier",
|
||||
"ident": "index"
|
||||
},
|
||||
"members": {
|
||||
"C": {
|
||||
"kind": "Class",
|
||||
"name": {
|
||||
"kind": "Identifier",
|
||||
"ident": "C",
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 13
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "public",
|
||||
"members": {},
|
||||
"abstract": false,
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
tools/mujs/tests/output/modules/class_exp_1/index.ts
Normal file
3
tools/mujs/tests/output/modules/class_exp_1/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export class C {
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"files": [
|
||||
"index.ts"
|
||||
]
|
||||
}
|
||||
|
4
tools/mujs/tests/output/modules/class_exp_def_1/Mu.json
Normal file
4
tools/mujs/tests/output/modules/class_exp_def_1/Mu.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "class_exp_def_1"
|
||||
}
|
||||
|
46
tools/mujs/tests/output/modules/class_exp_def_1/Mu.out.json
Normal file
46
tools/mujs/tests/output/modules/class_exp_def_1/Mu.out.json
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "class_exp_def_1",
|
||||
"modules": {
|
||||
"index": {
|
||||
"kind": "Module",
|
||||
"name": {
|
||||
"kind": "Identifier",
|
||||
"ident": "index"
|
||||
},
|
||||
"members": {
|
||||
"default": {
|
||||
"kind": "Class",
|
||||
"name": {
|
||||
"kind": "Identifier",
|
||||
"ident": "default"
|
||||
},
|
||||
"access": "public",
|
||||
"members": {},
|
||||
"abstract": false,
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
tools/mujs/tests/output/modules/class_exp_def_1/index.ts
Normal file
3
tools/mujs/tests/output/modules/class_exp_def_1/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default class {
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"files": [
|
||||
"index.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -81,7 +81,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"returnType": "TODO"
|
||||
"returnType": "TODO",
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
|
|
|
@ -81,7 +81,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"returnType": "TODO"
|
||||
"returnType": "TODO",
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
|
|
|
@ -70,7 +70,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"returnType": "TODO"
|
||||
"returnType": "TODO",
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
|
|
Loading…
Reference in a new issue