Cleanup; Initial switch support; Relooper interface fwiw

This commit is contained in:
dcodeIO
2017-10-11 17:03:22 +02:00
parent 50116acede
commit 6e98c52f76
12 changed files with 646 additions and 383 deletions

1
.gitignore vendored
View File

@ -2,4 +2,3 @@ node_modules/
npm-debug.* npm-debug.*
out/ out/
raw/ raw/

View File

@ -404,7 +404,7 @@ export abstract class Expression extends Node {
return expr; return expr;
} }
abstract serializeAsTree(sb: string[], indent: i32); abstract serializeAsTree(sb: string[], indent: i32): void;
} }
export const enum LiteralKind { export const enum LiteralKind {
@ -541,10 +541,10 @@ export class CallExpression extends Expression {
serialize(sb: string[]): void { serialize(sb: string[]): void {
this.expression.serialize(sb); this.expression.serialize(sb);
let i: i32, k: i32; let i: i32, k: i32 = this.typeArguments.length;
if (this.typeArguments.length) { if (k) {
sb.push("<"); sb.push("<");
for (i = 0, k = this.typeArguments.length; i < k; ++i) { for (i = 0; i < k; ++i) {
if (i > 0) if (i > 0)
sb.push(", "); sb.push(", ");
this.typeArguments[i].serialize(sb); this.typeArguments[i].serialize(sb);
@ -935,14 +935,14 @@ export abstract class Statement extends Node {
return stmt; return stmt;
} }
static createExport(modifiers: Modifier[], members: ExportMember[], path: string | null, range: Range): ExportStatement { static createExport(modifiers: Modifier[], members: ExportMember[], path: StringLiteralExpression | null, range: Range): ExportStatement {
const stmt: ExportStatement = new ExportStatement(); const stmt: ExportStatement = new ExportStatement();
stmt.range = range; stmt.range = range;
let i: i32, k: i32; let i: i32, k: i32;
for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt;
for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt;
stmt.path = path; stmt.path = path;
stmt.normalizedPath = path == null ? null : resolvePath(normalizePath(<string>path), range.source.normalizedPath); stmt.normalizedPath = path ? resolvePath(normalizePath(path.value), range.source.normalizedPath) : null;
return stmt; return stmt;
} }
@ -978,12 +978,12 @@ export abstract class Statement extends Node {
return stmt; return stmt;
} }
static createImport(declarations: ImportDeclaration[], path: string, range: Range): ImportStatement { static createImport(declarations: ImportDeclaration[], path: StringLiteralExpression, range: Range): ImportStatement {
const stmt: ImportStatement = new ImportStatement(); const stmt: ImportStatement = new ImportStatement();
stmt.range = range; stmt.range = range;
for (let i: i32 = 0, k: i32 = (stmt.declarations = declarations).length; i < k; ++i) declarations[i].parent = stmt; for (let i: i32 = 0, k: i32 = (stmt.declarations = declarations).length; i < k; ++i) declarations[i].parent = stmt;
stmt.path = path; stmt.path = path;
stmt.normalizedPath = resolvePath(normalizePath(path), range.source.normalizedPath); stmt.normalizedPath = resolvePath(normalizePath(path.value), range.source.normalizedPath);
return stmt; return stmt;
} }
@ -1196,7 +1196,17 @@ export class Source extends Node {
} }
export abstract class DeclarationStatement extends Statement { export abstract class DeclarationStatement extends Statement {
identifier: IdentifierExpression; identifier: IdentifierExpression;
modifiers: Modifier[] | null;
private _cachedInternalName: string | null = null;
globalExportName: string | null = null;
get internalName(): string {
if (this._cachedInternalName == null)
this._cachedInternalName = mangleInternalName(this);
return this._cachedInternalName;
}
} }
export class BlockStatement extends Statement { export class BlockStatement extends Statement {
@ -1225,7 +1235,7 @@ export class BreakStatement extends Statement {
serialize(sb: string[]): void { serialize(sb: string[]): void {
if (this.label) { if (this.label) {
sb.push("break "); sb.push("break ");
sb.push((<IdentifierExpression>this.label).name); (<IdentifierExpression>this.label).serialize(name);
} else } else
sb.push("break"); sb.push("break");
} }
@ -1234,7 +1244,6 @@ export class BreakStatement extends Statement {
export class ClassDeclaration extends DeclarationStatement { export class ClassDeclaration extends DeclarationStatement {
kind = NodeKind.CLASS; kind = NodeKind.CLASS;
modifiers: Modifier[];
typeParameters: TypeParameter[]; typeParameters: TypeParameter[];
extendsType: TypeNode | null; extendsType: TypeNode | null;
implementsTypes: TypeNode[]; implementsTypes: TypeNode[];
@ -1247,10 +1256,11 @@ export class ClassDeclaration extends DeclarationStatement {
this.decorators[i].serialize(sb); this.decorators[i].serialize(sb);
sb.push("\n"); sb.push("\n");
} }
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
sb.push("class "); sb.push("class ");
sb.push(this.identifier.name); sb.push(this.identifier.name);
if (this.typeParameters.length) { if (this.typeParameters.length) {
@ -1294,7 +1304,7 @@ export class ContinueStatement extends Statement {
serialize(sb: string[]): void { serialize(sb: string[]): void {
if (this.label) { if (this.label) {
sb.push("continue "); sb.push("continue ");
sb.push(this.label.name); (<IdentifierExpression>this.label).serialize(sb);
} else } else
sb.push("continue"); sb.push("continue");
} }
@ -1347,15 +1357,15 @@ export class EmptyStatement extends Statement {
export class EnumDeclaration extends DeclarationStatement { export class EnumDeclaration extends DeclarationStatement {
kind = NodeKind.ENUM; kind = NodeKind.ENUM;
modifiers: Modifier[];
members: EnumValueDeclaration[]; members: EnumValueDeclaration[];
serialize(sb: string[]): void { serialize(sb: string[]): void {
let i: i32, k: i32; let i: i32, k: i32;
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
sb.push("enum "); sb.push("enum ");
this.identifier.serialize(sb); this.identifier.serialize(sb);
sb.push(" {\n"); sb.push(" {\n");
@ -1371,6 +1381,7 @@ export class EnumDeclaration extends DeclarationStatement {
export class EnumValueDeclaration extends DeclarationStatement { export class EnumValueDeclaration extends DeclarationStatement {
kind = NodeKind.ENUMVALUE; kind = NodeKind.ENUMVALUE;
modifiers = null;
value: Expression | null; value: Expression | null;
serialize(sb: string[]): void { serialize(sb: string[]): void {
@ -1414,29 +1425,29 @@ export class ExportMember extends Node {
export class ExportStatement extends Statement { export class ExportStatement extends Statement {
kind = NodeKind.EXPORT; kind = NodeKind.EXPORT;
modifiers: Modifier[]; modifiers: Modifier[] | null;
members: ExportMember[]; members: ExportMember[];
path: string | null; path: StringLiteralExpression | null;
normalizedPath: string | null; normalizedPath: string | null;
serialize(sb: string[]): void { serialize(sb: string[]): void {
let i: i32, k: i32; let i: i32, k: i32;
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
sb.push("export {\n"); sb.push("export {\n");
for (i = 0, k = this.members.length; i < k; ++i) { for (i = 0, k = this.members.length; i < k; ++i) {
if (i > 0) if (i > 0)
sb.push(",\n"); sb.push(",\n");
this.members[i].serialize(sb); this.members[i].serialize(sb);
} }
if (this.path == null) if (this.path) {
sb.push("\n}");
else {
sb.push("\n} from "); sb.push("\n} from ");
sb.push(JSON.stringify(this.path)); this.path.serialize(sb);
} } else
sb.push("\n}");
} }
} }
@ -1453,7 +1464,6 @@ export class ExpressionStatement extends Statement {
export class FieldDeclaration extends DeclarationStatement { export class FieldDeclaration extends DeclarationStatement {
kind = NodeKind.FIELD; kind = NodeKind.FIELD;
modifiers: Modifier[];
type: TypeNode | null; type: TypeNode | null;
initializer: Expression | null; initializer: Expression | null;
decorators: DecoratorStatement[]; decorators: DecoratorStatement[];
@ -1464,10 +1474,11 @@ export class FieldDeclaration extends DeclarationStatement {
this.decorators[i].serialize(sb); this.decorators[i].serialize(sb);
sb.push("\n"); sb.push("\n");
} }
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
this.identifier.serialize(sb); this.identifier.serialize(sb);
if (this.type) { if (this.type) {
sb.push(": "); sb.push(": ");
@ -1510,7 +1521,6 @@ export class ForStatement extends Statement {
export class FunctionDeclaration extends DeclarationStatement { export class FunctionDeclaration extends DeclarationStatement {
kind = NodeKind.FUNCTION; kind = NodeKind.FUNCTION;
modifiers: Modifier[];
typeParameters: TypeParameter[]; typeParameters: TypeParameter[];
parameters: Parameter[]; parameters: Parameter[];
returnType: TypeNode | null; returnType: TypeNode | null;
@ -1523,10 +1533,11 @@ export class FunctionDeclaration extends DeclarationStatement {
this.decorators[i].serialize(sb); this.decorators[i].serialize(sb);
sb.push("\n"); sb.push("\n");
} }
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
sb.push("function "); sb.push("function ");
this.serializeCommon(sb); this.serializeCommon(sb);
} }
@ -1596,6 +1607,7 @@ export class IfStatement extends Statement {
export class ImportDeclaration extends DeclarationStatement { export class ImportDeclaration extends DeclarationStatement {
kind = NodeKind.IMPORTDECLARATION; kind = NodeKind.IMPORTDECLARATION;
modifiers = null;
externalIdentifier: IdentifierExpression; externalIdentifier: IdentifierExpression;
serialize(sb: string[]): void { serialize(sb: string[]): void {
@ -1611,7 +1623,7 @@ export class ImportStatement extends Statement {
kind = NodeKind.IMPORT; kind = NodeKind.IMPORT;
declarations: ImportDeclaration[]; declarations: ImportDeclaration[];
path: string; path: StringLiteralExpression;
normalizedPath: string; normalizedPath: string;
serialize(sb: string[]): void { serialize(sb: string[]): void {
@ -1622,24 +1634,24 @@ export class ImportStatement extends Statement {
this.declarations[i].serialize(sb); this.declarations[i].serialize(sb);
} }
sb.push("\n} from "); sb.push("\n} from ");
sb.push(JSON.stringify(this.path)); this.path.serialize(sb);
} }
} }
export class InterfaceDeclaration extends DeclarationStatement { export class InterfaceDeclaration extends DeclarationStatement {
kind = NodeKind.INTERFACE; kind = NodeKind.INTERFACE;
modifiers: Modifier[];
typeParameters: TypeParameter[]; typeParameters: TypeParameter[];
extendsType: TypeNode | null; extendsType: TypeNode | null;
members: Statement[]; members: Statement[];
serialize(sb: string[]): void { serialize(sb: string[]): void {
let i: i32, k: i32; let i: i32, k: i32;
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
sb.push("interface "); sb.push("interface ");
this.identifier.serialize(sb); this.identifier.serialize(sb);
if (this.typeParameters.length) { if (this.typeParameters.length) {
@ -1677,10 +1689,11 @@ export class MethodDeclaration extends FunctionDeclaration {
this.decorators[i].serialize(sb); this.decorators[i].serialize(sb);
sb.push("\n"); sb.push("\n");
} }
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
super.serializeCommon(sb); super.serializeCommon(sb);
} }
} }
@ -1688,15 +1701,15 @@ export class MethodDeclaration extends FunctionDeclaration {
export class NamespaceDeclaration extends DeclarationStatement { export class NamespaceDeclaration extends DeclarationStatement {
kind = NodeKind.NAMESPACE; kind = NodeKind.NAMESPACE;
modifiers: Modifier[];
members: Statement[]; members: Statement[];
serialize(sb: string[]): void { serialize(sb: string[]): void {
let i: i32, k: i32; let i: i32, k: i32;
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
} sb.push(" ");
}
sb.push("namespace "); sb.push("namespace ");
this.identifier.serialize(sb); this.identifier.serialize(sb);
sb.push(" {\n"); sb.push(" {\n");
@ -1862,6 +1875,7 @@ export class TryStatement extends Statement {
export class VariableDeclaration extends DeclarationStatement { export class VariableDeclaration extends DeclarationStatement {
kind = NodeKind.VARIABLEDECLARATION; kind = NodeKind.VARIABLEDECLARATION;
modifiers = null;
type: TypeNode | null; type: TypeNode | null;
initializer: Expression | null; initializer: Expression | null;
@ -1881,18 +1895,19 @@ export class VariableDeclaration extends DeclarationStatement {
export class VariableStatement extends Statement { export class VariableStatement extends Statement {
kind = NodeKind.VARIABLE; kind = NodeKind.VARIABLE;
modifiers: Modifier[]; modifiers: Modifier[] | null;
declarations: VariableDeclaration[]; declarations: VariableDeclaration[];
serialize(sb: string[]): void { serialize(sb: string[]): void {
let isConst: bool = false; let isConst: bool = false;
let i: i32, k: i32; let i: i32, k: i32;
for (i = 0, k = this.modifiers.length; i < k; ++i) { if (this.modifiers)
this.modifiers[i].serialize(sb); for (i = 0, k = (<Modifier[]>this.modifiers).length; i < k; ++i) {
sb.push(" "); (<Modifier[]>this.modifiers)[i].serialize(sb);
if (this.modifiers[i].modifierKind == ModifierKind.CONST) sb.push(" ");
isConst = true; if ((<Modifier[]>this.modifiers)[i].modifierKind == ModifierKind.CONST)
} isConst = true;
}
if (!isConst) if (!isConst)
sb.push("let "); sb.push("let ");
for (i = 0, k = this.declarations.length; i < k; ++i) { for (i = 0, k = this.declarations.length; i < k; ++i) {
@ -1917,15 +1932,12 @@ export class WhileStatement extends Statement {
} }
} }
export function isDeclarationStatement(kind: NodeKind): bool { export function hasModifier(kind: ModifierKind, modifiers: Modifier[] | null): bool {
return kind == NodeKind.CLASS if (modifiers)
|| kind == NodeKind.ENUM for (let i: i32 = 0, k: i32 = (<Modifier[]>modifiers).length; i < k; ++i)
|| kind == NodeKind.ENUMVALUE if ((<Modifier[]>modifiers)[i].modifierKind == kind)
|| kind == NodeKind.FIELD return true;
|| kind == NodeKind.FUNCTION return false;
|| kind == NodeKind.METHOD
|| kind == NodeKind.NAMESPACE
|| kind == NodeKind.VARIABLEDECLARATION;
} }
export function serialize(node: Node, indent: i32 = 0): string { export function serialize(node: Node, indent: i32 = 0): string {
@ -1934,6 +1946,17 @@ export function serialize(node: Node, indent: i32 = 0): string {
return sb.join(""); return sb.join("");
} }
export function mangleInternalName(declaration: DeclarationStatement): string {
let name: string = declaration.identifier.name;
if (!declaration.parent)
return name;
if (declaration.parent.kind == NodeKind.CLASS)
return (<ClassDeclaration>declaration.parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? "." : "#") + name;
if (declaration.parent.kind == NodeKind.NAMESPACE || declaration.parent.kind == NodeKind.ENUM)
return (<DeclarationStatement>declaration.parent).internalName + "." + name;
return declaration.range.source.normalizedPath + "/" + name;
}
function builderEndsWith(sb: string[], code: CharCode): bool { function builderEndsWith(sb: string[], code: CharCode): bool {
if (sb.length) { if (sb.length) {
const last: string = sb[sb.length - 1]; const last: string = sb[sb.length - 1];

View File

@ -534,6 +534,65 @@ export class Module {
_BinaryenModuleDispose(this.ref); _BinaryenModuleDispose(this.ref);
_free(this.lit); _free(this.lit);
} }
createRelooper(): Relooper {
return this.noEmit ? Relooper.createStub(this) : Relooper.create(this);
}
}
export class Relooper {
module: Module;
ref: RelooperRef;
noEmit: bool;
static create(module: Module): Relooper {
const relooper: Relooper = new Relooper();
relooper.module = module;
relooper.ref = _RelooperCreate();
relooper.noEmit = false;
return relooper;
}
static createStub(module: Module): Relooper {
const relooper: Relooper = new Relooper();
relooper.module = module;
relooper.ref = 0;
relooper.noEmit = true;
return relooper;
}
private constructor() {}
addBlock(code: BinaryenExpressionRef): RelooperBlockRef {
if (this.noEmit) return 0;
return _RelooperAddBlock(this.ref, code);
}
addBranch(from: RelooperBlockRef, to: RelooperBlockRef, condition: BinaryenExpressionRef = 0, code: BinaryenExpressionRef = 0): void {
if (this.noEmit) return;
_RelooperAddBranch(from, to, condition, code);
}
addBlockWithSwitch(code: BinaryenExpressionRef, condition: BinaryenExpressionRef): RelooperBlockRef {
if (this.noEmit) return 0;
return _RelooperAddBlockWithSwitch(this.ref, code, condition);
}
addBranchForSwitch(from: RelooperBlockRef, to: RelooperBlockRef, indexes: i32[], code: BinaryenExpressionRef = 0): void {
if (this.noEmit) return;
const cArr: CArray<i32> = allocI32Array(indexes);
try {
_RelooperAddBranchForSwitch(from, to, cArr, indexes.length, code);
} finally {
_free(cArr);
}
}
renderAndDispose(entry: RelooperBlockRef, labelHelper: BinaryenIndex): BinaryenExpressionRef {
if (this.noEmit) return 0;
return _RelooperRenderAndDispose(this.ref, entry, labelHelper, this.module.ref);
}
} }
// helpers // helpers

View File

@ -1,6 +1,5 @@
import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp, Type as BinaryenType } from "./binaryen"; import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp, Type as BinaryenType, Relooper } from "./binaryen";
import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics";
import { hasModifier } from "./parser";
import { Program } from "./program"; import { Program } from "./program";
import { CharCode, I64, U64, normalizePath, sb } from "./util"; import { CharCode, I64, U64, normalizePath, sb } from "./util";
import { Token } from "./tokenizer"; import { Token } from "./tokenizer";
@ -35,6 +34,7 @@ import {
NamespaceDeclaration, NamespaceDeclaration,
ReturnStatement, ReturnStatement,
Statement, Statement,
SwitchCase,
SwitchStatement, SwitchStatement,
ThrowStatement, ThrowStatement,
TryStatement, TryStatement,
@ -60,7 +60,9 @@ import {
SelectExpression, SelectExpression,
StringLiteralExpression, StringLiteralExpression,
UnaryPostfixExpression, UnaryPostfixExpression,
UnaryPrefixExpression UnaryPrefixExpression,
hasModifier
} from "./ast"; } from "./ast";
import { import {
@ -75,17 +77,21 @@ import {
} from "./types"; } from "./types";
export enum Target { export enum Target {
/** WebAssembly with 32-bit pointers. */
WASM32, WASM32,
/** WebAssembly with 64-bit pointers. Experimental / not supported by any runtime yet. */
WASM64 WASM64
} }
export class Options { export class Options {
/** WebAssembly target. Defaults to {@link Target.WASM32}. */
target: Target = Target.WASM32; target: Target = Target.WASM32;
/** If true, performs compilation as usual but doesn't produce any output (all calls to Binaryen are nops). */
noEmit: bool = false; noEmit: bool = false;
/** If true, compiles everything instead of just reachable code. */
noTreeShaking: bool = false;
} }
const VOID: BinaryenExpressionRef = 0;
export class Compiler extends DiagnosticEmitter { export class Compiler extends DiagnosticEmitter {
program: Program; program: Program;
@ -98,10 +104,12 @@ export class Compiler extends DiagnosticEmitter {
currentType: Type = Type.void; currentType: Type = Type.void;
currentClass: ClassType | null = null; currentClass: ClassType | null = null;
currentFunction: FunctionType = this.startFunction; currentFunction: FunctionType = this.startFunction;
disallowContinue: bool = true;
memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL
memorySegments: MemorySegment[] = new Array(); memorySegments: MemorySegment[] = new Array();
files: Set<string> = new Set();
classes: Map<string,ClassType> = new Map(); classes: Map<string,ClassType> = new Map();
enums: Set<string> = new Set(); enums: Set<string> = new Set();
functions: Map<string,FunctionType> = new Map(); functions: Map<string,FunctionType> = new Map();
@ -117,7 +125,6 @@ export class Compiler extends DiagnosticEmitter {
this.program = program; this.program = program;
this.options = options ? options : new Options(); this.options = options ? options : new Options();
this.module = this.options.noEmit ? Module.createStub() : Module.create(); this.module = this.options.noEmit ? Module.createStub() : Module.create();
// noEmit performs compilation as usual but all binaryen methods are nops instead
} }
compile(): Module { compile(): Module {
@ -126,56 +133,11 @@ export class Compiler extends DiagnosticEmitter {
// initialize lookup maps // initialize lookup maps
program.initialize(this.options.target); program.initialize(this.options.target);
// start by compiling entry file exports // compile entry file (exactly one, usually)
const entrySource: Source = program.sources[0]; for (let i: i32 = 0, k = program.sources.length; i < k; ++i) {
for (let i: i32 = 0, k: i32 = entrySource.statements.length; i < k; ++i) { const source: Source = program.sources[i];
const statement: Statement = entrySource.statements[i]; if (source.isEntry)
switch (statement.kind) { this.compileFile(source);
case NodeKind.CLASS:
if (hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>statement).modifiers) && !(<ClassDeclaration>statement).typeParameters.length)
this.compileClass(<ClassDeclaration>statement, []);
break;
case NodeKind.ENUM:
if (hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>statement).modifiers))
this.compileEnum(<EnumDeclaration>statement);
break;
case NodeKind.FUNCTION:
if (hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>statement).modifiers) && !(<FunctionDeclaration>statement).typeParameters.length)
this.compileFunction(<FunctionDeclaration>statement, []);
break;
case NodeKind.IMPORT:
break;
case NodeKind.NAMESPACE:
if (hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>statement).modifiers))
this.compileNamespace(<NamespaceDeclaration>statement);
break;
case NodeKind.VARIABLE:
if (hasModifier(ModifierKind.EXPORT, (<VariableStatement>statement).modifiers))
this.compileGlobals(<VariableStatement>statement);
break;
case NodeKind.EXPORT: {
this.compileExports(<ExportStatement>statement);
break;
}
default: {
const previousClass: ClassType | null = this.currentClass;
const previousFunction: FunctionType = this.currentFunction;
this.currentClass = null;
this.currentFunction = this.startFunction;
this.startFunctionBody.push(this.compileStatement(statement));
this.currentClass = previousClass;
this.currentFunction = previousFunction;
break;
}
}
} }
// make start function if not empty // make start function if not empty
@ -184,7 +146,7 @@ export class Compiler extends DiagnosticEmitter {
if (!typeRef) if (!typeRef)
typeRef = this.module.addFunctionType("v", BinaryenType.None, []); typeRef = this.module.addFunctionType("v", BinaryenType.None, []);
this.module.setStart( this.module.setStart(
this.module.addFunction("", typeRef, typesToBinaryenTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) this.module.addFunction("start", typeRef, typesToBinaryenTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody))
); );
} }
@ -202,48 +164,126 @@ export class Compiler extends DiagnosticEmitter {
return this.module; return this.module;
} }
compileFile(source: Source): void {
if (this.files.has(source.normalizedPath))
return;
this.files.add(source.normalizedPath);
const isEntry: bool = source.isEntry;
const noTreeShaking: bool = this.options.noTreeShaking;
for (let i: i32 = 0, k: i32 = source.statements.length; i < k; ++i) {
const statement: Statement = source.statements[i];
switch (statement.kind) {
case NodeKind.CLASS:
if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>statement).modifiers)) && !(<ClassDeclaration>statement).typeParameters.length)
this.compileClass(<ClassDeclaration>statement, []);
break;
case NodeKind.ENUM:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>statement).modifiers))
this.compileEnum(<EnumDeclaration>statement);
break;
case NodeKind.FUNCTION:
if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>statement).modifiers)) && !(<FunctionDeclaration>statement).typeParameters.length)
this.compileFunction(<FunctionDeclaration>statement, []);
break;
case NodeKind.IMPORT:
this.compileFileByPath((<ImportStatement>statement).normalizedPath, (<ImportStatement>statement).path);
break;
case NodeKind.NAMESPACE:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>statement).modifiers))
this.compileNamespace(<NamespaceDeclaration>statement);
break;
case NodeKind.VARIABLE:
if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (<VariableStatement>statement).modifiers))
this.compileGlobals(<VariableStatement>statement);
break;
case NodeKind.EXPORT:
if ((<ExportStatement>statement).path)
this.compileFileByPath((<StringLiteralExpression>(<ExportStatement>statement).path).value, <StringLiteralExpression>(<ExportStatement>statement).path);
if (noTreeShaking || isEntry)
this.compileExports(<ExportStatement>statement);
break;
// otherwise a top-level statement that is part of the start function's body
default: {
const previousClass: ClassType | null = this.currentClass;
const previousFunction: FunctionType = this.currentFunction;
this.currentClass = null;
this.currentFunction = this.startFunction;
this.startFunctionBody.push(this.compileStatement(statement));
this.currentClass = previousClass;
this.currentFunction = previousFunction;
break;
}
}
}
}
compileFileByPath(normalizedPath: string, reportNode: Node): void {
for (let j: i32 = 0, l: i32 = this.program.sources.length; j < l; ++j) {
const importedSource: Source = this.program.sources[j];
if (importedSource.normalizedPath == normalizedPath) {
this.compileFile(importedSource);
return;
}
}
this.error(DiagnosticCode.File_0_not_found, reportNode.range, normalizedPath);
}
compileClass(declaration: ClassDeclaration, typeArguments: Type[]): void { compileClass(declaration: ClassDeclaration, typeArguments: Type[]): void {
throw new Error("not implemented"); throw new Error("not implemented");
} }
compileEnum(declaration: EnumDeclaration): void { compileEnum(declaration: EnumDeclaration): void {
const name: string = this.program.mangleInternalName(declaration); const internalName: string = declaration.internalName;
if (this.enums.has(name)) { if (this.enums.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
return; return;
} }
this.enums.add(internalName);
const valueDeclarations: EnumValueDeclaration[] = declaration.members; const valueDeclarations: EnumValueDeclaration[] = declaration.members;
let previousValueName: string | null = null; let previousValueName: string | null = null;
const isExport: bool = declaration.range.source.isEntry && hasModifier(ModifierKind.EXPORT, declaration.modifiers);
for (let i: i32 = 0, k: i32 = valueDeclarations.length; i < k; ++i) for (let i: i32 = 0, k: i32 = valueDeclarations.length; i < k; ++i)
previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName, isExport); previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName);
this.enums.add(name);
} }
compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null, isExport: bool): string { compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null): string {
const name: string = this.program.mangleInternalName(declaration); const internalName: string = declaration.internalName;
let initializer: BinaryenExpressionRef = declaration.value ? this.compileExpression(<Expression>declaration.value, Type.i32) : 0; // TODO: WASM does not support complex initializers for globals yet, hence we make such globals mutable and initialize in start
let initializeInStart: bool = declaration.value ? (<Expression>declaration.value).kind != NodeKind.LITERAL : true; let initializer: BinaryenExpressionRef;
// TODO: WASM does not support binary initializers for globals yet, hence me make them mutable and initialize in start let initializeInStart: bool;
if (!initializer) { if (declaration.value) {
if (previousName == null) { initializer = this.compileExpression(<Expression>declaration.value, Type.i32);
initializer = this.module.createI32(0); initializeInStart = declaration.value.kind != NodeKind.LITERAL;
initializeInStart = false; } else if (previousName == null) {
} else { initializer = this.module.createI32(0);
initializer = this.module.createBinary(BinaryOp.AddI32, initializeInStart = false;
this.module.createGetGlobal(previousName, BinaryenType.I32), } else {
this.module.createI32(1) initializer = this.module.createBinary(BinaryOp.AddI32,
); this.module.createGetGlobal(previousName, BinaryenType.I32),
} this.module.createI32(1)
);
initializeInStart = true;
} }
if (initializeInStart) { if (initializeInStart) {
this.module.addGlobal(name, BinaryenType.I32, true, this.module.createI32(-1)); this.module.addGlobal(internalName, BinaryenType.I32, true, this.module.createI32(-1));
this.startFunctionBody.push(this.module.createSetGlobal(name, initializer)); this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else } else
this.module.addGlobal(name, BinaryenType.I32, false, initializer); this.module.addGlobal(internalName, BinaryenType.I32, false, initializer);
// TODO: WASM does not support exporting globals yet (the following produces invalid code referencing a non-existent function)
// this.module.addExport(name, (<EnumDeclaration>declaration.parent).identifier.name + "." + declaration.identifier.name); // export if applicable
return name; if (declaration.parent && (<Node>declaration.parent).kind == NodeKind.ENUM && (<EnumDeclaration>declaration.parent).globalExportName != null) {
// TODO: WASM does not support exporting globals yet
// this.module.addExport(internalName, <string>(<EnumDeclaration>declaration.parent).exportName);
}
return internalName;
} }
checkTypeArguments(typeParameters: TypeParameter[], typeArguments: Type[], reportNode: Node | null = null): bool { checkTypeArguments(typeParameters: TypeParameter[], typeArguments: Type[], reportNode: Node | null = null): bool {
@ -252,6 +292,7 @@ export class Compiler extends DiagnosticEmitter {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, (<Node>reportNode).range, typeParameters.length.toString(10), typeArguments.length.toString(10)); this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, (<Node>reportNode).range, typeParameters.length.toString(10), typeArguments.length.toString(10));
return false; return false;
} }
// TODO: check class types, arrays
// TODO: check TypeParameter#extendsType // TODO: check TypeParameter#extendsType
return true; return true;
} }
@ -266,7 +307,7 @@ export class Compiler extends DiagnosticEmitter {
if (!this.checkTypeArguments(declaration.typeParameters, typeArguments, reportNode)) // reports if requested if (!this.checkTypeArguments(declaration.typeParameters, typeArguments, reportNode)) // reports if requested
return; return;
let globalName: string = this.program.mangleInternalName(declaration); let internalName: string = declaration.internalName;
// inherit type arguments, i.e. from class // inherit type arguments, i.e. from class
const typeArgumentsMap: Map<string,Type> = new Map(); const typeArgumentsMap: Map<string,Type> = new Map();
@ -279,10 +320,10 @@ export class Compiler extends DiagnosticEmitter {
if (k) { if (k) {
for (i = 0; i < k; ++i) for (i = 0; i < k; ++i)
typeArgumentsMap.set(declaration.typeParameters[i].identifier.name, typeArguments[i]); typeArgumentsMap.set(declaration.typeParameters[i].identifier.name, typeArguments[i]);
globalName += typeArgumentsToString(typeArguments); internalName += typeArgumentsToString(typeArguments);
} }
if (this.functions.has(globalName)) { if (this.functions.has(internalName)) {
if (reportNode) if (reportNode)
this.error(DiagnosticCode.Duplicate_function_implementation, (<Node>reportNode).range); this.error(DiagnosticCode.Duplicate_function_implementation, (<Node>reportNode).range);
return; return;
@ -319,96 +360,100 @@ export class Compiler extends DiagnosticEmitter {
// compile statements // compile statements
const functionType: FunctionType = new FunctionType(typeArguments, parameterTypes, returnType, parameterNames); const functionType: FunctionType = new FunctionType(typeArguments, parameterTypes, returnType, parameterNames);
this.functions.set(globalName, functionType); this.functions.set(internalName, functionType);
const previousFunction: FunctionType = this.currentFunction; const previousFunction: FunctionType = this.currentFunction;
this.currentFunction = functionType; this.currentFunction = functionType;
const statements: Statement[] = <Statement[]>declaration.statements; const stmts: BinaryenExpressionRef[] = this.compileStatements(<Statement[]>declaration.statements);
k = statements.length;
const body: BinaryenExpressionRef[] = new Array(k);
let hasErrors: bool = false;
for (i = 0; i < k; ++i)
if (!(body[i] = this.compileStatement(statements[i])))
hasErrors = true;
this.currentFunction = previousFunction; this.currentFunction = previousFunction;
if (hasErrors)
return;
// create function // create function
const binaryenResultType: BinaryenType = typeToBinaryenType(returnType); const binaryenResultType: BinaryenType = typeToBinaryenType(returnType);
const binaryenParamTypes: BinaryenType[] = typesToBinaryenTypes(parameterTypes); const binaryenParamTypes: BinaryenType[] = typesToBinaryenTypes(parameterTypes);
let binaryenTypeRef: BinaryenFunctionTypeRef = this.module.getFunctionTypeBySignature(binaryenResultType, binaryenParamTypes); let binaryenTypeRef: BinaryenFunctionTypeRef = this.module.getFunctionTypeBySignature(binaryenResultType, binaryenParamTypes);
if (!binaryenTypeRef) if (!binaryenTypeRef)
binaryenTypeRef = this.module.addFunctionType(typesToSignatureName(parameterTypes, returnType), binaryenResultType, binaryenParamTypes); binaryenTypeRef = this.module.addFunctionType(typesToSignatureName(parameterTypes, returnType), binaryenResultType, binaryenParamTypes);
this.module.addFunction(globalName, binaryenTypeRef, typesToBinaryenTypes(functionType.additionalLocals), this.module.createBlock(null, body)); this.module.addFunction(internalName, binaryenTypeRef, typesToBinaryenTypes(functionType.additionalLocals), this.module.createBlock(null, stmts, BinaryenType.None));
if (declaration.range.source.isEntry && hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.module.addExport(globalName, declaration.identifier.name); // export if applicable
if (declaration.globalExportName != null)
this.module.addExport(internalName, <string>declaration.globalExportName);
} }
compileGlobals(statement: VariableStatement): void { compileGlobals(statement: VariableStatement): void {
const declarations: VariableDeclaration[] = statement.declarations; const declarations: VariableDeclaration[] = statement.declarations;
const isConst: bool = hasModifier(ModifierKind.CONST, statement.modifiers); const isConst: bool = hasModifier(ModifierKind.CONST, statement.modifiers);
const isExport: bool = statement.range.source.isEntry && hasModifier(ModifierKind.EXPORT, statement.modifiers);
for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i)
this.compileGlobal(declarations[i], isConst, isExport); this.compileGlobal(declarations[i], isConst);
} }
compileGlobal(declaration: VariableDeclaration, isConst: bool, isExport: bool): void { compileGlobal(declaration: VariableDeclaration, isConst: bool): void {
const type: Type | null = declaration.type ? this.resolveType(<TypeNode>declaration.type) : null; // reports const type: Type | null = declaration.type ? this.resolveType(<TypeNode>declaration.type) : null; // reports
if (!type) if (!type)
return; return;
const name: string = this.program.mangleInternalName(declaration); const internalName: string = declaration.internalName;
if (this.globals.has(name)) { if (this.globals.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
return; return;
} }
const binaryenType: BinaryenType = typeToBinaryenType(<Type>type); const binaryenType: BinaryenType = typeToBinaryenType(<Type>type);
const initializer: BinaryenExpressionRef = declaration.initializer ? this.compileExpression(<Expression>declaration.initializer, <Type>type) : typeToBinaryenZero(this.module, <Type>type); // TODO: WASM does not support complex initializers for globals yet, hence me such globals mutable and initialize in start
let initializeInStart: bool = declaration.initializer ? (<Expression>declaration.initializer).kind != NodeKind.LITERAL : false; let initializer: BinaryenExportRef;
// TODO: WASM does not support binary initializers for globals yet, hence me make them mutable and initialize in start let initializeInStart: bool;
if (declaration.initializer) {
initializer = this.compileExpression(<Expression>declaration.initializer, <Type>type);
initializeInStart = (<Expression>declaration.initializer).kind != NodeKind.LITERAL;
} else {
initializer = typeToBinaryenZero(this.module, <Type>type);
initializeInStart = false;
}
if (initializeInStart) { if (initializeInStart) {
this.module.addGlobal(name, binaryenType, false, typeToBinaryenZero(this.module, <Type>type)); this.module.addGlobal(internalName, binaryenType, true, typeToBinaryenZero(this.module, <Type>type));
this.startFunctionBody.push(initializer); this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else } else
this.module.addGlobal(name, binaryenType, !isConst, initializer); this.module.addGlobal(internalName, binaryenType, !isConst, initializer);
// TODO: WASM does not support exporting globals yet (the following produces invalid code referencing a non-existent function) this.globals.set(internalName, <Type>type);
// this.module.addExport(name, declaration.identifier.name);
this.globals.set(name, <Type>type); // export if applicable
if (declaration.globalExportName != null) {
// TODO: WASM does not support exporting globals yet
// this.module.addExport(internalName, <string>declaration.exportName);
}
} }
compileNamespace(declaration: NamespaceDeclaration): void { compileNamespace(declaration: NamespaceDeclaration): void {
const members: Statement[] = declaration.members; const members: Statement[] = declaration.members;
const noTreeShaking: bool = this.options.noTreeShaking;
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
const member: Statement = members[i]; const member: Statement = members[i];
switch (member.kind) { switch (member.kind) {
case NodeKind.CLASS: case NodeKind.CLASS:
if (!(<ClassDeclaration>member).typeParameters.length) if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>member).modifiers)) && !(<ClassDeclaration>member).typeParameters.length)
this.compileClass(<ClassDeclaration>member, []); this.compileClass(<ClassDeclaration>member, []);
break; break;
case NodeKind.ENUM: case NodeKind.ENUM:
this.compileEnum(<EnumDeclaration>member); if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>member).modifiers))
this.compileEnum(<EnumDeclaration>member);
break; break;
case NodeKind.FUNCTION: case NodeKind.FUNCTION:
if (!(<FunctionDeclaration>member).typeParameters.length) if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>member).modifiers)) && !(<FunctionDeclaration>member).typeParameters.length)
this.compileFunction(<FunctionDeclaration>member, []); this.compileFunction(<FunctionDeclaration>member, []);
break; break;
case NodeKind.NAMESPACE: case NodeKind.NAMESPACE:
this.compileNamespace(<NamespaceDeclaration>member); if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>member).modifiers))
this.compileNamespace(<NamespaceDeclaration>member);
break; break;
case NodeKind.VARIABLE: case NodeKind.VARIABLE:
this.compileGlobals(<VariableStatement>member); if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<VariableStatement>member).modifiers))
this.compileGlobals(<VariableStatement>member);
break; break;
// TODO: some form of internal visibility?
// case NodeKind.EXPORT:
default: default:
throw new Error("unexpected namespace member kind"); throw new Error("unexpected namespace member");
} }
} }
throw new Error("not implemented"); throw new Error("not implemented");
@ -416,14 +461,14 @@ export class Compiler extends DiagnosticEmitter {
compileExports(statement: ExportStatement): void { compileExports(statement: ExportStatement): void {
const members: ExportMember[] = statement.members; const members: ExportMember[] = statement.members;
const normalizedPath: string | null = statement.path == null ? null : normalizePath(<string>statement.path); const normalizedPath: string | null = statement.path ? normalizePath(<string>(<StringLiteralExpression>statement.path).value) : statement.range.source.normalizedPath;
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) for (let i: i32 = 0, k: i32 = members.length; i < k; ++i)
this.compileExport(members[i], normalizedPath); this.compileExport(members[i], normalizedPath);
} }
compileExport(member: ExportMember, normalizedPath: string | null): void { compileExport(member: ExportMember, normalizedPath: string): void {
const exportName: string = normalizedPath + "/" + member.externalIdentifier.name; const internalExportName: string = normalizedPath + "/" + member.identifier.name;
const declaration: DeclarationStatement | null = <DeclarationStatement | null>this.program.exports.get(exportName); const declaration: DeclarationStatement | null = <DeclarationStatement | null>this.program.exports.get(internalExportName);
if (declaration) { if (declaration) {
switch ((<DeclarationStatement>declaration).kind) { switch ((<DeclarationStatement>declaration).kind) {
@ -446,7 +491,7 @@ export class Compiler extends DiagnosticEmitter {
break; break;
case NodeKind.VARIABLEDECLARATION: case NodeKind.VARIABLEDECLARATION:
this.compileGlobal(<VariableDeclaration>declaration, hasModifier(ModifierKind.CONST, (<VariableStatement>declaration.parent).modifiers), member.range.source.isEntry); this.compileGlobal(<VariableDeclaration>declaration, hasModifier(ModifierKind.CONST, (<VariableStatement>declaration.parent).modifiers));
break; break;
default: default:
@ -554,9 +599,7 @@ export class Compiler extends DiagnosticEmitter {
if (this.functions.has(globalName)) if (this.functions.has(globalName))
return <FunctionType>this.functions.get(globalName); return <FunctionType>this.functions.get(globalName);
this.error(DiagnosticCode.Cannot_find_name_0, expression.range, globalName); this.error(DiagnosticCode.Cannot_find_name_0, expression.range, globalName);
return null; } else {
}
if (expression.kind == NodeKind.PROPERTYACCESS) {
throw new Error("not implemented"); throw new Error("not implemented");
} }
return null; return null;
@ -609,28 +652,36 @@ export class Compiler extends DiagnosticEmitter {
case NodeKind.WHILE: case NodeKind.WHILE:
return this.compileWhileStatement(<WhileStatement>statement); return this.compileWhileStatement(<WhileStatement>statement);
} }
throw new Error("not implemented"); throw new Error("unexpected statement kind");
}
compileStatements(statements: Statement[]): BinaryenExpressionRef[] {
const k: i32 = statements.length;
const stmts: BinaryenExpressionRef[] = new Array(k);
for (let i: i32 = 0; i < k; ++i)
stmts[i] = this.compileStatement(statements[i]);
return stmts;
} }
compileBlockStatement(statement: BlockStatement): BinaryenExpressionRef { compileBlockStatement(statement: BlockStatement): BinaryenExpressionRef {
const substatements: Statement[] = statement.statements; return this.module.createBlock(null, this.compileStatements(statement.statements), BinaryenType.None);
const children: BinaryenExpressionRef[] = new Array(substatements.length);
for (let i: i32 = 0, k: i32 = substatements.length; i < k; ++i)
children[i] = this.compileStatement(substatements[i]);
return this.module.createBlock(null, children, BinaryenType.None);
} }
compileBreakStatement(statement: BreakStatement): BinaryenExpressionRef { compileBreakStatement(statement: BreakStatement): BinaryenExpressionRef {
if (statement.label)
throw new Error("not implemented");
const context: string | null = this.currentFunction.breakContext; const context: string | null = this.currentFunction.breakContext;
if (context) if (context != null)
return this.module.createBreak("break$" + (<string>context)); return this.module.createBreak("break$" + (<string>context));
this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range); this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range);
return this.module.createUnreachable(); return this.module.createUnreachable();
} }
compileContinueStatement(statement: ContinueStatement): BinaryenExpressionRef { compileContinueStatement(statement: ContinueStatement): BinaryenExpressionRef {
if (statement.label)
throw new Error("not implemented");
const context: string | null = this.currentFunction.breakContext; const context: string | null = this.currentFunction.breakContext;
if (context) if (context != null && !this.disallowContinue)
return this.module.createBreak("continue$" + (<string>context)); return this.module.createBreak("continue$" + (<string>context));
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range); this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
return this.module.createUnreachable(); return this.module.createUnreachable();
@ -648,8 +699,8 @@ export class Compiler extends DiagnosticEmitter {
this.module.createBlock(null, [ this.module.createBlock(null, [
body, body,
this.module.createBreak(continueLabel, condition) this.module.createBreak(continueLabel, condition)
])) ], BinaryenType.None))
]); ], BinaryenType.None);
} }
compileEmptyStatement(statement: EmptyStatement): BinaryenExpressionRef { compileEmptyStatement(statement: EmptyStatement): BinaryenExpressionRef {
@ -676,9 +727,9 @@ export class Compiler extends DiagnosticEmitter {
body, body,
incrementor, incrementor,
this.module.createBreak(continueLabel) this.module.createBreak(continueLabel)
])) ], BinaryenType.None))
])) ], BinaryenType.None))
]); ], BinaryenType.None);
} }
compileIfStatement(statement: IfStatement): BinaryenExpressionRef { compileIfStatement(statement: IfStatement): BinaryenExpressionRef {
@ -697,7 +748,57 @@ export class Compiler extends DiagnosticEmitter {
} }
compileSwitchStatement(statement: SwitchStatement): BinaryenExpressionRef { compileSwitchStatement(statement: SwitchStatement): BinaryenExpressionRef {
throw new Error("not implemented"); const context: string = this.currentFunction.enterBreakContext();
const previousDisallowContinue: bool = this.disallowContinue;
this.disallowContinue = true;
// introduce a local for evaluating the condition (exactly once)
const localIndex: i32 = this.currentFunction.addLocal(Type.i32);
let i: i32, k: i32 = statement.cases.length;
// prepend initializer to inner block
const breaks: BinaryenExpressionRef[] = new Array(1 + k);
breaks[0] = this.module.createSetLocal(localIndex, this.compileExpression(statement.expression, Type.i32)); // initializer
// make one br_if per (possibly dynamic) labeled case
// TODO: take advantage of br_table where labels are known to be (sequential) constant (ideally Binaryen's optimizer would)
let breakIndex: i32 = 1;
let defaultIndex: i32 = -1;
for (i = 0; i < k; ++i) {
const case_: SwitchCase = statement.cases[i];
if (case_.label) {
breaks[breakIndex++] = this.module.createBreak("case" + i.toString(10) + "$" + context, this.module.createBinary(BinaryOp.EqI32,
this.module.createGetLocal(localIndex, BinaryenType.I32),
this.compileExpression(case_.label, Type.i32)
));
} else
defaultIndex = i;
}
// otherwise br to default respectively out of the switch if there is no default case
breaks[breakIndex] = this.module.createBreak((defaultIndex >= 0
? "case" + defaultIndex.toString(10)
: "break"
) + "$" + context);
// nest blocks in order
let currentBlock: BinaryenExpressionRef = this.module.createBlock("case0$" + context, breaks, BinaryenType.None);
for (i = 0; i < k; ++i) {
const case_: SwitchCase = statement.cases[i];
const nextLabel: string = i == k - 1
? "break$" + context
: "case" + (i + 1).toString(10) + "$" + context;
const l: i32 = case_.statements.length;
const body: BinaryenExpressionRef[] = new Array(1 + l);
body[0] = currentBlock;
for (let j: i32 = 0; j < l; ++j)
body[j + 1] = this.compileStatement(case_.statements[j]);
currentBlock = this.module.createBlock(nextLabel, body, BinaryenType.None);
}
this.currentFunction.leaveBreakContext();
this.disallowContinue = previousDisallowContinue;
return currentBlock;
} }
compileThrowStatement(statement: ThrowStatement): BinaryenExpressionRef { compileThrowStatement(statement: ThrowStatement): BinaryenExpressionRef {
@ -734,24 +835,24 @@ export class Compiler extends DiagnosticEmitter {
} }
} }
} }
return initializers.length ? this.module.createBlock(null, initializers) : this.module.createNop(); return initializers.length ? this.module.createBlock(null, initializers, BinaryenType.None) : this.module.createNop();
} }
compileWhileStatement(statement: WhileStatement): BinaryenExpressionRef { compileWhileStatement(statement: WhileStatement): BinaryenExpressionRef {
const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32);
const label: string = this.currentFunction.enterBreakContext(); const label: string = this.currentFunction.enterBreakContext();
const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32);
const breakLabel: string = "break$" + label; const breakLabel: string = "break$" + label;
const continueLabel: string = "continue$" + label; const continueLabel: string = "continue$" + label;
const body: BinaryenExpressionRef = this.compileStatement(statement.statement); const body: BinaryenExpressionRef = this.compileStatement(statement.statement);
this.currentFunction.leaveBreakContext(); this.currentFunction.leaveBreakContext();
return this.module.createBlock(breakLabel, [ return this.module.createBlock(breakLabel, [
this.module.createLoop(continueLabel, this.module.createLoop(continueLabel,
this.module.createIf(condition, this.module.createBlock("", [ this.module.createIf(condition, this.module.createBlock(null, [
body, body,
this.module.createBreak(continueLabel) this.module.createBreak(continueLabel)
])) ], BinaryenType.None))
) )
]); ], BinaryenType.None);
} }
// expressions // expressions
@ -779,6 +880,10 @@ export class Compiler extends DiagnosticEmitter {
break; break;
case NodeKind.IDENTIFIER: case NodeKind.IDENTIFIER:
case NodeKind.FALSE:
case NodeKind.NULL:
case NodeKind.THIS:
case NodeKind.TRUE:
expr = this.compileIdentifierExpression(<IdentifierExpression>expression, contextualType); expr = this.compileIdentifierExpression(<IdentifierExpression>expression, contextualType);
break; break;
@ -1254,19 +1359,76 @@ export class Compiler extends DiagnosticEmitter {
} }
compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): BinaryenExpressionRef { compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): BinaryenExpressionRef {
const name: string = expression.name;
// null
if (expression.kind == NodeKind.NULL) {
if (contextualType.classType) // keep contextualType
return this.options.target == Target.WASM64 ? this.module.createI64(0, 0) : this.module.createI32(0);
if (this.options.target == Target.WASM64) {
this.currentType = Type.u64;
return this.module.createI64(0, 0);
} else {
this.currentType = Type.u32;
return this.module.createI32(0);
}
// true
} else if (expression.kind == NodeKind.TRUE) {
this.currentType = Type.bool;
return this.module.createI32(1);
// false
} else if (expression.kind == NodeKind.FALSE) {
this.currentType = Type.bool;
return this.module.createI32(0);
// this
} else if (expression.kind == NodeKind.THIS) {
if (/* TODO: this.currentFunction.isInstance &&*/ this.currentClass) {
this.currentType = this.currentClass.type;
return this.module.createGetLocal(0, typeToBinaryenType(this.currentType));
}
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
this.currentType = this.options.target == Target.WASM64 ? Type.u64 : Type.u32;
return this.module.createUnreachable();
}
const globalName: string = expression.name; // same as local variable name
// local variable
const locals: Map<string,LocalType> = this.currentFunction.locals; const locals: Map<string,LocalType> = this.currentFunction.locals;
if (locals.has(name)) { if (locals.has(globalName)) {
const local: LocalType = <LocalType>locals.get(name); const local: LocalType = <LocalType>locals.get(globalName);
this.currentType = local.type; this.currentType = local.type;
return this.module.createGetLocal(local.index, typeToBinaryenType(this.currentType)); return this.module.createGetLocal(local.index, typeToBinaryenType(this.currentType));
} }
const globals: Map<string,Type> = this.globals;
if (globals.has(name)) { // global in local file
this.currentType = <Type>globals.get(name); const localName: string = expression.range.source.normalizedPath + "/" + globalName;
return this.module.createGetGlobal(name, typeToBinaryenType(this.currentType)); let determinedName: string = localName;
if (this.program.names.has(localName)) {
const declaration: DeclarationStatement = <DeclarationStatement>this.program.names.get(localName);
if (declaration.kind == NodeKind.VARIABLEDECLARATION) {
if (!this.globals.has(declaration.internalName))
this.compileGlobal(<VariableDeclaration>declaration, hasModifier(ModifierKind.CONST, declaration.modifiers));
}
// global across files
} else if (this.program.names.has(globalName)) {
const declaration: DeclarationStatement = <DeclarationStatement>this.program.names.get(globalName);
if (declaration.kind == NodeKind.VARIABLEDECLARATION) {
if (!this.globals.has(declaration.internalName))
this.compileGlobal(<VariableDeclaration>declaration, hasModifier(ModifierKind.CONST, declaration.modifiers));
determinedName = globalName;
}
} }
this.error(DiagnosticCode.Cannot_find_name_0, expression.range, name);
const globals: Map<string,Type> = this.globals;
if (globals.has(determinedName)) {
this.currentType = <Type>globals.get(determinedName);
return this.module.createGetGlobal(determinedName, typeToBinaryenType(this.currentType));
}
this.error(DiagnosticCode.Cannot_find_name_0, expression.range, determinedName);
return this.module.createUnreachable(); return this.module.createUnreachable();
} }

View File

@ -52,7 +52,8 @@ export enum DiagnosticCode {
The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357,
Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391, Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391,
Duplicate_function_implementation = 2393, Duplicate_function_implementation = 2393,
Expected_0_type_arguments_but_got_1 = 2558 Expected_0_type_arguments_but_got_1 = 2558,
File_0_not_found = 6054
} }
export function diagnosticCodeToString(code: DiagnosticCode): string { export function diagnosticCodeToString(code: DiagnosticCode): string {
@ -109,6 +110,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2391: return "Function implementation is missing or not immediately following the declaration."; case 2391: return "Function implementation is missing or not immediately following the declaration.";
case 2393: return "Duplicate function implementation."; case 2393: return "Duplicate function implementation.";
case 2558: return "Expected {0} type arguments, but got {1}."; case 2558: return "Expected {0} type arguments, but got {1}.";
case 6054: return "File '{0}' not found.";
default: return ""; default: return "";
} }
} }

View File

@ -52,5 +52,7 @@
"The operand of an increment or decrement operator must be a variable or a property access.": 2357, "The operand of an increment or decrement operator must be a variable or a property access.": 2357,
"Function implementation is missing or not immediately following the declaration.": 2391, "Function implementation is missing or not immediately following the declaration.": 2391,
"Duplicate function implementation.": 2393, "Duplicate function implementation.": 2393,
"Expected {0} type arguments, but got {1}.": 2558 "Expected {0} type arguments, but got {1}.": 2558,
"File '{0}' not found.": 6054
} }

View File

@ -1,12 +1,10 @@
const globalScope = typeof window !== "undefined" && window const globalScope: any = typeof window !== "undefined" && window || typeof global !== "undefined" && global || self;
|| typeof global !== "undefined" && global
|| self;
globalScope["store"] = function store_u8(ptr, val) { globalScope["store"] = function store_u8(ptr: number, val: number) {
binaryen.HEAPU8[ptr] = val; binaryen.HEAPU8[ptr] = val;
}; };
globalScope["load"] = function load_u8(ptr) { globalScope["load"] = function load_u8(ptr: number) {
return binaryen.HEAPU8[ptr]; return binaryen.HEAPU8[ptr];
}; };

View File

@ -23,10 +23,13 @@ import {
AssertionKind, AssertionKind,
Expression, Expression,
IdentifierExpression, IdentifierExpression,
StringLiteralExpression,
// statements // statements
BlockStatement, BlockStatement,
BreakStatement,
ClassDeclaration, ClassDeclaration,
ContinueStatement,
DecoratorStatement, DecoratorStatement,
DoStatement, DoStatement,
EnumDeclaration, EnumDeclaration,
@ -55,7 +58,9 @@ import {
TypeParameter, TypeParameter,
VariableStatement, VariableStatement,
VariableDeclaration, VariableDeclaration,
WhileStatement WhileStatement,
hasModifier
} from "./ast"; } from "./ast";
@ -202,7 +207,7 @@ export class Parser extends DiagnosticEmitter {
finish(): Program { finish(): Program {
if (this.backlog.length) if (this.backlog.length)
throw new Error("backlog is not empty"); throw new Error("backlog is not empty");
this.backlog = new Array(0); this.backlog = [];
this.seenlog.clear(); this.seenlog.clear();
return this.program; return this.program;
} }
@ -536,7 +541,7 @@ export class Parser extends DiagnosticEmitter {
if (!typeParameters) if (!typeParameters)
return null; return null;
} else } else
typeParameters = new Array(0); typeParameters = [];
if (!tn.skip(Token.OPENPAREN)) { if (!tn.skip(Token.OPENPAREN)) {
this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "("); this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "(");
return null; return null;
@ -565,7 +570,7 @@ export class Parser extends DiagnosticEmitter {
} }
} else if (!isDeclare) } else if (!isDeclare)
this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, tn.range(tn.pos)); this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, tn.range(tn.pos));
const ret: FunctionDeclaration = Statement.createFunction(modifiers, identifier, typeParameters, <Parameter[]>parameters, returnType, statements, decorators ? <DecoratorStatement[]>decorators : new Array(0), tn.range(startPos, tn.pos)); const ret: FunctionDeclaration = Statement.createFunction(modifiers, identifier, typeParameters, <Parameter[]>parameters, returnType, statements, decorators ? <DecoratorStatement[]>decorators : [], tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON); tn.skip(Token.SEMICOLON);
return ret; return ret;
} }
@ -587,7 +592,7 @@ export class Parser extends DiagnosticEmitter {
if (!typeParameters) if (!typeParameters)
return null; return null;
} else } else
typeParameters = new Array(0); typeParameters = [];
let extendsType: TypeNode | null = null; let extendsType: TypeNode | null = null;
if (tn.skip(Token.EXTENDS)) { if (tn.skip(Token.EXTENDS)) {
@ -617,7 +622,7 @@ export class Parser extends DiagnosticEmitter {
members.push(<DeclarationStatement>member); members.push(<DeclarationStatement>member);
} while (!tn.skip(Token.CLOSEBRACE)); } while (!tn.skip(Token.CLOSEBRACE));
} }
return Statement.createClass(modifiers, identifier, <TypeParameter[]>typeParameters, extendsType, implementsTypes, members, decorators ? <DecoratorStatement[]>decorators : new Array(0), tn.range(startPos, tn.pos)); return Statement.createClass(modifiers, identifier, <TypeParameter[]>typeParameters, extendsType, implementsTypes, members, decorators ? <DecoratorStatement[]>decorators : [], tn.range(startPos, tn.pos));
} else } else
this.error(DiagnosticCode._0_expected, tn.range(), "{"); this.error(DiagnosticCode._0_expected, tn.range(), "{");
} else } else
@ -665,7 +670,7 @@ export class Parser extends DiagnosticEmitter {
if (!typeParameters) if (!typeParameters)
return null; return null;
} else } else
typeParameters = new Array(0); typeParameters = [];
// method: '(' Parameters (':' Type)? '{' Statement* '}' ';'? // method: '(' Parameters (':' Type)? '{' Statement* '}' ';'?
if (tn.skip(Token.OPENPAREN)) { if (tn.skip(Token.OPENPAREN)) {
@ -746,10 +751,10 @@ export class Parser extends DiagnosticEmitter {
return null; return null;
} }
} }
let path: string | null = null; let path: StringLiteralExpression | null = null;
if (tn.skip(Token.FROM)) { if (tn.skip(Token.FROM)) {
if (tn.skip(Token.STRINGLITERAL)) if (tn.skip(Token.STRINGLITERAL))
path = tn.readString(); path = Expression.createStringLiteral(tn.readString(), tn.range());
else { else {
this.error(DiagnosticCode.String_literal_expected, tn.range()); this.error(DiagnosticCode.String_literal_expected, tn.range());
return null; return null;
@ -805,7 +810,7 @@ export class Parser extends DiagnosticEmitter {
} }
if (tn.skip(Token.FROM)) { if (tn.skip(Token.FROM)) {
if (tn.skip(Token.STRINGLITERAL)) { if (tn.skip(Token.STRINGLITERAL)) {
const path: string = tn.readString(); const path: StringLiteralExpression = Expression.createStringLiteral(tn.readString(), tn.range());
const ret: ImportStatement = Statement.createImport(members, path, Range.join(startRange, tn.range())); const ret: ImportStatement = Statement.createImport(members, path, Range.join(startRange, tn.range()));
if (!this.seenlog.has(ret.normalizedPath)) { if (!this.seenlog.has(ret.normalizedPath)) {
this.backlog.push(ret.normalizedPath); this.backlog.push(ret.normalizedPath);
@ -866,9 +871,15 @@ export class Parser extends DiagnosticEmitter {
const token: Token = tn.next(); const token: Token = tn.next();
switch (token) { switch (token) {
case Token.BREAK:
return this.parseBreak(tn);
case Token.CONST: case Token.CONST:
return this.parseVariable(tn, [ Statement.createModifier(ModifierKind.CONST, tn.range()) ]); return this.parseVariable(tn, [ Statement.createModifier(ModifierKind.CONST, tn.range()) ]);
case Token.CONTINUE:
return this.parseContinue(tn);
case Token.DO: case Token.DO:
return this.parseDoStatement(tn); return this.parseDoStatement(tn);
@ -926,6 +937,30 @@ export class Parser extends DiagnosticEmitter {
return ret; return ret;
} }
parseBreak(tn: Tokenizer): BreakStatement | null {
// at 'break': Identifier? ';'?
let identifier: IdentifierExpression | null = null;
if (tn.peek(true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) {
tn.next(true);
identifier = Expression.createIdentifier(tn.readIdentifier(), tn.range());
}
const ret: ContinueStatement = Statement.createBreak(identifier, tn.range());
tn.skip(Token.SEMICOLON);
return ret;
}
parseContinue(tn: Tokenizer): ContinueStatement | null {
// at 'continue': Identifier? ';'?
let identifier: IdentifierExpression | null = null;
if (tn.peek(true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) {
tn.next(true);
identifier = Expression.createIdentifier(tn.readIdentifier(), tn.range());
}
const ret: ContinueStatement = Statement.createContinue(identifier, tn.range());
tn.skip(Token.SEMICOLON);
return ret;
}
parseDoStatement(tn: Tokenizer): DoStatement | null { parseDoStatement(tn: Tokenizer): DoStatement | null {
// at 'do': Statement 'while' '(' Expression ')' ';'? // at 'do': Statement 'while' '(' Expression ')' ';'?
const startPos: i32 = tn.tokenPos; const startPos: i32 = tn.tokenPos;
@ -1027,7 +1062,7 @@ export class Parser extends DiagnosticEmitter {
return null; return null;
if (tn.skip(Token.CLOSEPAREN)) { if (tn.skip(Token.CLOSEPAREN)) {
if (tn.skip(Token.OPENBRACE)) { if (tn.skip(Token.OPENBRACE)) {
const cases: SwitchCase[] = new Array(0); const cases: SwitchCase[] = [];
while (!tn.skip(Token.CLOSEBRACE)) { while (!tn.skip(Token.CLOSEBRACE)) {
const case_: SwitchCase | null = this.parseSwitchCase(tn); const case_: SwitchCase | null = this.parseSwitchCase(tn);
if (!case_) if (!case_)
@ -1232,8 +1267,9 @@ export class Parser extends DiagnosticEmitter {
} }
// UnaryPrefixExpression // UnaryPrefixExpression
if ((<Expression>operand).kind != NodeKind.IDENTIFIER && (<Expression>operand).kind != NodeKind.ELEMENTACCESS && (<Expression>operand).kind != NodeKind.PROPERTYACCESS) if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS)
this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, (<Expression>operand).range); if ((<Expression>operand).kind != NodeKind.IDENTIFIER && (<Expression>operand).kind != NodeKind.ELEMENTACCESS && (<Expression>operand).kind != NodeKind.PROPERTYACCESS)
this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, (<Expression>operand).range);
return Expression.createUnaryPrefix(token, <Expression>operand, tn.range(startPos, tn.pos)); return Expression.createUnaryPrefix(token, <Expression>operand, tn.range(startPos, tn.pos));
} }
@ -1315,20 +1351,20 @@ export class Parser extends DiagnosticEmitter {
} }
} }
tryParseTypeArgumentsBeforeArguments(tn: Tokenizer): IdentifierExpression[] | null { tryParseTypeArgumentsBeforeArguments(tn: Tokenizer): TypeNode[] | null {
// at '<': Identifier (',' Identifier)* '>' '(' // at '<': Identifier (',' Identifier)* '>' '('
tn.mark(); tn.mark();
if (!tn.skip(Token.LESSTHAN)) if (!tn.skip(Token.LESSTHAN))
return null; return null;
const typeArguments: IdentifierExpression[] = new Array(); const typeArguments: TypeNode[] = [];
do { do {
const token: Token = tn.next(); const type: TypeNode | null = this.parseType(tn);
if (token != Token.IDENTIFIER) { if (!type) {
tn.reset(); tn.reset();
return null; return null;
} }
typeArguments.push(Expression.createIdentifier(tn.readIdentifier(), tn.range())); typeArguments.push(type);
} while (tn.skip(Token.COMMA)); } while (tn.skip(Token.COMMA));
if (!(tn.skip(Token.GREATERTHAN) && tn.skip(Token.OPENPAREN))) { if (!(tn.skip(Token.GREATERTHAN) && tn.skip(Token.OPENPAREN))) {
tn.reset(); tn.reset();
@ -1360,16 +1396,16 @@ export class Parser extends DiagnosticEmitter {
if (!expr) if (!expr)
return null; return null;
const startPos: i32 = expr.range.start; const startPos: i32 = expr.range.start;
// CallExpression // CallExpression
const typeArguments: IdentifierExpression[] | null = this.tryParseTypeArgumentsBeforeArguments(tn); const typeArguments: TypeNode[] | null = this.tryParseTypeArgumentsBeforeArguments(tn);
// there might be better ways to distinguish a LESSTHAN from a CALL // there might be better ways to distinguish a LESSTHAN from a CALL with type arguments
if (typeArguments || tn.skip(Token.OPENPAREN)) { if (typeArguments || tn.skip(Token.OPENPAREN)) {
const args: Expression[] | null = this.parseArguments(tn); const args: Expression[] | null = this.parseArguments(tn);
if (!args) if (!args)
return null; return null;
expr = Expression.createCall(expr, typeArguments ? <IdentifierExpression[]>typeArguments : new Array(0), args, tn.range(startPos, tn.pos)); expr = Expression.createCall(expr, <TypeNode[]>(typeArguments ? typeArguments : []), args, tn.range(startPos, tn.pos));
} }
let token: Token; let token: Token;
@ -1625,14 +1661,6 @@ function addModifier(modifier: Modifier, modifiers: Modifier[] | null): Modifier
return modifiers; return modifiers;
} }
export function hasModifier(kind: ModifierKind, modifiers: Modifier[] | null): bool {
if (modifiers != null)
for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i)
if (modifiers[i].modifierKind == kind)
return true;
return false;
}
function getModifier(kind: ModifierKind, modifiers: Modifier[]): Modifier { function getModifier(kind: ModifierKind, modifiers: Modifier[]): Modifier {
for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i) for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i)
if (modifiers[i].modifierKind == kind) if (modifiers[i].modifierKind == kind)

View File

@ -1,6 +1,5 @@
import { Target } from "./compiler"; import { Target } from "./compiler";
import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics";
import { hasModifier } from "./parser";
import { Type } from "./types"; import { Type } from "./types";
import { import {
@ -24,12 +23,15 @@ import {
NamespaceDeclaration, NamespaceDeclaration,
Statement, Statement,
VariableDeclaration, VariableDeclaration,
VariableStatement VariableStatement,
hasModifier
} from "./ast"; } from "./ast";
class QueuedExport { class QueuedExport {
importName: string; isForeign: bool;
referencedName: string;
member: ExportMember; member: ExportMember;
} }
@ -47,12 +49,12 @@ export class Program extends DiagnosticEmitter {
diagnosticsOffset: i32 = 0; diagnosticsOffset: i32 = 0;
target: Target = Target.WASM32; target: Target = Target.WASM32;
/** Internal map of names to declarations. */ /** Map of internal names to declarations. */
names: Map<string,DeclarationStatement> = new Map(); names: Map<string,DeclarationStatement> = new Map();
/** Separate map of internal type names to declarations. */ /** Separate map of internal type names to declarations. */
types: Map<string,Type> = typesStub; types: Map<string,Type> = typesStub;
/** Separate map of internal export names to declarations. */ /** Separate map of internal export names to declarations. */
exports: Map<string,DeclarationStatement> = new Map(); exports: Map<string,DeclarationStatement> = new Map(); // not global exports
constructor(diagnostics: DiagnosticMessage[] | null = null) { constructor(diagnostics: DiagnosticMessage[] | null = null) {
super(diagnostics); super(diagnostics);
@ -81,7 +83,7 @@ export class Program extends DiagnosticEmitter {
const queuedExports: Map<string,QueuedExport> = new Map(); const queuedExports: Map<string,QueuedExport> = new Map();
const queuedImports: QueuedImport[] = new Array(); const queuedImports: QueuedImport[] = new Array();
// build initial lookup maps of internal and export names to declarations // build initial lookup maps of internal names to declarations
for (let i: i32 = 0, k: i32 = this.sources.length; i < k; ++i) { for (let i: i32 = 0, k: i32 = this.sources.length; i < k; ++i) {
const source: Source = this.sources[i]; const source: Source = this.sources[i];
const statements: Statement[] = source.statements; const statements: Statement[] = source.statements;
@ -125,21 +127,36 @@ export class Program extends DiagnosticEmitter {
} }
// at this point queued exports should be resolvable // at this point queued exports should be resolvable
for (let [exportName, queuedExport] of queuedExports.entries()) { // export { add }
const seen: Set<QueuedExport> = new Set(); // export { sub } from "./other"
while (queuedExports.has(queuedExport.importName)) { for (let [exportName, queuedExport] of queuedExports) {
queuedExport = <QueuedExport>queuedExports.get(queuedExport.importName); if (queuedExport.isForeign) {
if (seen.has(queuedExport)) const seen: Set<QueuedExport> = new Set();
break; while (queuedExports.has(queuedExport.referencedName)) {
seen.add(queuedExport); queuedExport = <QueuedExport>queuedExports.get(queuedExport.referencedName);
if (seen.has(queuedExport))
break;
seen.add(queuedExport);
}
if (this.exports.has(queuedExport.referencedName)) {
const declaration: DeclarationStatement = <DeclarationStatement>this.exports.get(queuedExport.referencedName);
this.addExport(exportName, declaration);
if (queuedExport.member.range.source.isEntry)
declaration.globalExportName = queuedExport.member.externalIdentifier.name;
} else
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.referencedName);
} else /* local */ {
if (this.names.has(queuedExport.referencedName)) {
const declaration: DeclarationStatement = <DeclarationStatement>this.names.get(queuedExport.referencedName);
this.addExport(exportName, declaration);
if (queuedExport.member.range.source.isEntry)
declaration.globalExportName = queuedExport.member.externalIdentifier.name;
} else
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.referencedName);
} }
if (this.exports.has(queuedExport.importName))
this.addExport(exportName, <DeclarationStatement>this.exports.get(queuedExport.importName));
else
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.importName);
} }
// at this point also queued imports should be resolvable // at this point queued imports should be resolvable as well
for (let i: i32 = 0, k: i32 = queuedImports.length; i < k; ++i) { for (let i: i32 = 0, k: i32 = queuedImports.length; i < k; ++i) {
const queuedImport: QueuedImport = queuedImports[i]; const queuedImport: QueuedImport = queuedImports[i];
const internalName: string = queuedImport.internalName; const internalName: string = queuedImport.internalName;
@ -147,7 +164,7 @@ export class Program extends DiagnosticEmitter {
let importName: string = queuedImport.importName; let importName: string = queuedImport.importName;
while (queuedExports.has(importName)) { while (queuedExports.has(importName)) {
const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(importName); const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(importName);
importName = queuedExport.importName; importName = queuedExport.referencedName;
if (seen.has(queuedExport)) if (seen.has(queuedExport))
break; break;
seen.add(queuedExport); seen.add(queuedExport);
@ -167,7 +184,7 @@ export class Program extends DiagnosticEmitter {
} }
private initializeClass(declaration: ClassDeclaration): void { private initializeClass(declaration: ClassDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
this.addName(internalName, declaration); this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration); this.addExport(/* same as */internalName, declaration);
@ -191,12 +208,11 @@ export class Program extends DiagnosticEmitter {
} }
private initializeField(declaration: FieldDeclaration): void { private initializeField(declaration: FieldDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); this.addName(declaration.internalName, declaration);
this.addName(internalName, declaration);
} }
private initializeEnum(declaration: EnumDeclaration): void { private initializeEnum(declaration: EnumDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
this.addName(internalName, declaration); this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration); this.addExport(/* same as */internalName, declaration);
@ -206,8 +222,7 @@ export class Program extends DiagnosticEmitter {
} }
private initializeEnumValue(declaration: EnumValueDeclaration): void { private initializeEnumValue(declaration: EnumValueDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); this.addName(declaration.internalName, declaration);
this.addName(internalName, declaration);
} }
private initializeExports(statement: ExportStatement, queuedExports: Map<string,QueuedExport>): void { private initializeExports(statement: ExportStatement, queuedExports: Map<string,QueuedExport>): void {
@ -222,16 +237,20 @@ export class Program extends DiagnosticEmitter {
this.error(DiagnosticCode.Duplicate_identifier_0, member.externalIdentifier.range, exportName); this.error(DiagnosticCode.Duplicate_identifier_0, member.externalIdentifier.range, exportName);
else { else {
const queuedExport: QueuedExport = new QueuedExport(); const queuedExport: QueuedExport = new QueuedExport();
queuedExport.importName = normalizedPath == null if (normalizedPath == null) {
? member.range.source.normalizedPath + "/" + member.identifier.name // local queuedExport.isForeign = false;
: (<string>normalizedPath) + "/" + member.identifier.name; // re-export queuedExport.referencedName = member.range.source.normalizedPath + "/" + member.identifier.name;
} else {
queuedExport.isForeign = true;
queuedExport.referencedName = (<string>normalizedPath) + "/" + member.identifier.name;
}
queuedExport.member = member; queuedExport.member = member;
queuedExports.set(exportName, queuedExport); queuedExports.set(exportName, queuedExport);
} }
} }
private initializeFunction(declaration: FunctionDeclaration): void { private initializeFunction(declaration: FunctionDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
this.addName(internalName, declaration); this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration); this.addExport(/* same as */internalName, declaration);
@ -251,12 +270,12 @@ export class Program extends DiagnosticEmitter {
const seen: Set<QueuedExport> = new Set(); const seen: Set<QueuedExport> = new Set();
while (queuedExports.has(resolvedImportName)) { while (queuedExports.has(resolvedImportName)) {
const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(resolvedImportName); const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(resolvedImportName);
resolvedImportName = queuedExport.importName; resolvedImportName = queuedExport.referencedName;
if (seen.has(queuedExport)) if (seen.has(queuedExport))
break; break;
seen.add(queuedExport); seen.add(queuedExport);
} }
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
if (this.exports.has(resolvedImportName)) { // resolvable right away if (this.exports.has(resolvedImportName)) { // resolvable right away
if (this.names.has(internalName)) if (this.names.has(internalName))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
@ -272,7 +291,7 @@ export class Program extends DiagnosticEmitter {
} }
private initializeInterface(declaration: InterfaceDeclaration): void { private initializeInterface(declaration: InterfaceDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
this.addName(internalName, declaration); this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration); this.addExport(/* same as */internalName, declaration);
@ -296,12 +315,11 @@ export class Program extends DiagnosticEmitter {
} }
private initializeMethod(declaration: MethodDeclaration): void { private initializeMethod(declaration: MethodDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); this.addName(declaration.internalName, declaration);
this.addName(internalName, declaration);
} }
private initializeNamespace(declaration: NamespaceDeclaration): void { private initializeNamespace(declaration: NamespaceDeclaration): void {
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
this.addName(internalName, declaration); this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration); this.addExport(/* same as */internalName, declaration);
@ -345,7 +363,7 @@ export class Program extends DiagnosticEmitter {
const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers); const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers);
for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) {
const declaration: VariableDeclaration = declarations[i]; const declaration: VariableDeclaration = declarations[i];
const internalName: string = this.mangleInternalName(declaration); const internalName: string = declaration.internalName;
this.addName(internalName, declaration); this.addName(internalName, declaration);
if (isExport) if (isExport)
this.addExport(/* same as */internalName, declaration); this.addExport(/* same as */internalName, declaration);
@ -362,51 +380,10 @@ export class Program extends DiagnosticEmitter {
private addExport(exportName: string, declaration: DeclarationStatement): void { private addExport(exportName: string, declaration: DeclarationStatement): void {
if (this.exports.has(exportName)) if (this.exports.has(exportName))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, exportName); // recoverable this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, exportName); // recoverable
else else {
this.exports.set(exportName, declaration); this.exports.set(exportName, declaration);
} if (declaration.range.source.isEntry)
declaration.globalExportName = declaration.identifier.name;
mangleInternalName(declaration: DeclarationStatement): string {
let name: string = declaration.identifier.name;
let parent: Node | null = declaration.parent;
if (parent) {
switch (parent.kind) {
case NodeKind.SOURCE:
return (<Source>parent).normalizedPath + "/" + name;
case NodeKind.CLASS: {
if (
(declaration.kind == NodeKind.FIELD && !hasModifier(ModifierKind.STATIC, (<FieldDeclaration>declaration).modifiers)) ||
(declaration.kind == NodeKind.METHOD && !hasModifier(ModifierKind.STATIC, (<MethodDeclaration>declaration).modifiers))
)
return this.mangleInternalName(<DeclarationStatement>parent) + "#" + name;
// otherwise fall through
}
case NodeKind.ENUM:
case NodeKind.ENUMVALUE:
case NodeKind.NAMESPACE:
return this.mangleInternalName(<DeclarationStatement>parent) + "." + name;
case NodeKind.IMPORT: {
const importParent: Node | null = (<ImportStatement>parent).parent;
if (importParent && importParent.kind == NodeKind.SOURCE)
return (<Source>importParent).path + "/" + name;
break;
}
case NodeKind.VARIABLE: {
const variableParent: Node | null = (<VariableStatement>parent).parent;
if (variableParent) {
if (variableParent.kind == NodeKind.SOURCE)
return <Source>variableParent == this.sources[0] ? name : (<Source>variableParent).path + "/" + name;
if (variableParent.kind == NodeKind.NAMESPACE)
return this.mangleInternalName(<DeclarationStatement>variableParent) + "." + name;
}
break;
}
}
} }
throw new Error("unexpected parent");
} }
} }

View File

@ -13,6 +13,9 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"strictNullChecks": true, "strictNullChecks": true,
"alwaysStrict": true, "alwaysStrict": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"noImplicitThis": true,
"outDir": "../out" "outDir": "../out"
}, },
"files": [ "files": [

View File

@ -28,25 +28,18 @@ mod.addExport("lit", "lit");
mod.addGlobal("42", Type.I32, false, mod.createI32(42)); mod.addGlobal("42", Type.I32, false, mod.createI32(42));
const aSwitch = mod.addFunctionType("i", Type.I32, []); const aSwitch = mod.addFunctionType("ii", Type.I32, [ Type.I32 ]);
mod.addFunction("aSwitch", aSwitch, [], mod.createBlock("", [ const rl = mod.createRelooper();
mod.createBlock("a", [ const b0 = rl.addBlockWithSwitch(mod.createNop(), mod.createGetLocal(0, Type.I32));
mod.createBlock("b", [ let b1, b2, b3;
mod.createBlock("c", [ rl.addBranchForSwitch(b0, b2 = rl.addBlock(mod.createReturn(mod.createI32(1))), [1]); // indexed branch
mod.createBlock("d", [ rl.addBranchForSwitch(b0, b3 = rl.addBlock(mod.createReturn(mod.createI32(2))), [2]); // indexed branch
mod.createSwitch( rl.addBranch(b0, b1 = rl.addBlock(mod.createDrop(mod.createI32(0)))); // default branch
["a", "b", "c"], rl.addBranch(b1, b2);
"d",
mod.createI32(4) mod.addFunction("aSwitch", aSwitch, [ Type.I32 ], mod.createBlock(null, [
) rl.renderAndDispose(b0, 1),
]), mod.createUnreachable()
mod.createReturn(mod.createI32(3))
]),
mod.createReturn(mod.createI32(2))
]),
mod.createReturn(mod.createI32(1))
]),
mod.createReturn(mod.createI32(0))
])); ]));
mod.addExport("aSwitch", "aSwitch"); mod.addExport("aSwitch", "aSwitch");

View File

@ -11,12 +11,37 @@ import { Parser } from "../src/parser";
]); */ ]); */
const files: Map<string,string> = new Map([ const files: Map<string,string> = new Map([
["main", ` ["main",
export function add(a: i32, b: i32): i32 { let c: i32 = a + b; return c; } `
`] function add(a: i32, b: i32): i32 { return a + b; };
export { add };
export { sub as notadd } from "../other";
2+3;
export function switchMe(n: i32): i32 {
switch (n) {
case 0:
return 0;
default:
return 2;
case 1:
return 1;
case -1:
break;
}
return -1;
}
`],
["../other",
`
export function sub(a: i32, b: i32): i32 { return a - b + c; };
let c: i32 = 42 >> 31;
1+2;
`]
]); ]);
const parser = new Parser(); const parser = new Parser();
parser.parseFile(<string>files.get("main"), "main", true); parser.parseFile(<string>files.get("main"), "main", true);
do { do {
let nextFile = parser.nextFile(); let nextFile = parser.nextFile();
@ -30,15 +55,7 @@ const program = parser.finish();
const compiler = new Compiler(program); const compiler = new Compiler(program);
const module = compiler.compile(); const module = compiler.compile();
console.log("names", program.names.keys()); // module.optimize();
console.log("exports", program.exports.keys()); module.validate();
module.optimize();
// module.validate(); // global initializers can't use i32.add etc. yet
if (!module.noEmit) if (!module.noEmit)
_BinaryenModulePrint(module.ref); _BinaryenModulePrint(module.ref);
/* console.log("--- statements ---");
compiler.statements.forEach(stmt => {
_BinaryenExpressionPrint(stmt);
}); */