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.*
out/
raw/

View File

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

View File

@ -534,6 +534,65 @@ export class Module {
_BinaryenModuleDispose(this.ref);
_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

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 { hasModifier } from "./parser";
import { Program } from "./program";
import { CharCode, I64, U64, normalizePath, sb } from "./util";
import { Token } from "./tokenizer";
@ -35,6 +34,7 @@ import {
NamespaceDeclaration,
ReturnStatement,
Statement,
SwitchCase,
SwitchStatement,
ThrowStatement,
TryStatement,
@ -60,7 +60,9 @@ import {
SelectExpression,
StringLiteralExpression,
UnaryPostfixExpression,
UnaryPrefixExpression
UnaryPrefixExpression,
hasModifier
} from "./ast";
import {
@ -75,17 +77,21 @@ import {
} from "./types";
export enum Target {
/** WebAssembly with 32-bit pointers. */
WASM32,
/** WebAssembly with 64-bit pointers. Experimental / not supported by any runtime yet. */
WASM64
}
export class Options {
/** WebAssembly target. Defaults to {@link 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;
/** If true, compiles everything instead of just reachable code. */
noTreeShaking: bool = false;
}
const VOID: BinaryenExpressionRef = 0;
export class Compiler extends DiagnosticEmitter {
program: Program;
@ -98,10 +104,12 @@ export class Compiler extends DiagnosticEmitter {
currentType: Type = Type.void;
currentClass: ClassType | null = null;
currentFunction: FunctionType = this.startFunction;
disallowContinue: bool = true;
memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL
memorySegments: MemorySegment[] = new Array();
files: Set<string> = new Set();
classes: Map<string,ClassType> = new Map();
enums: Set<string> = new Set();
functions: Map<string,FunctionType> = new Map();
@ -117,7 +125,6 @@ export class Compiler extends DiagnosticEmitter {
this.program = program;
this.options = options ? options : new Options();
this.module = this.options.noEmit ? Module.createStub() : Module.create();
// noEmit performs compilation as usual but all binaryen methods are nops instead
}
compile(): Module {
@ -126,56 +133,11 @@ export class Compiler extends DiagnosticEmitter {
// initialize lookup maps
program.initialize(this.options.target);
// start by compiling entry file exports
const entrySource: Source = program.sources[0];
for (let i: i32 = 0, k: i32 = entrySource.statements.length; i < k; ++i) {
const statement: Statement = entrySource.statements[i];
switch (statement.kind) {
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;
}
}
// compile entry file (exactly one, usually)
for (let i: i32 = 0, k = program.sources.length; i < k; ++i) {
const source: Source = program.sources[i];
if (source.isEntry)
this.compileFile(source);
}
// make start function if not empty
@ -184,7 +146,7 @@ export class Compiler extends DiagnosticEmitter {
if (!typeRef)
typeRef = this.module.addFunctionType("v", BinaryenType.None, []);
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;
}
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 {
throw new Error("not implemented");
}
compileEnum(declaration: EnumDeclaration): void {
const name: string = this.program.mangleInternalName(declaration);
if (this.enums.has(name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name);
const internalName: string = declaration.internalName;
if (this.enums.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
return;
}
this.enums.add(internalName);
const valueDeclarations: EnumValueDeclaration[] = declaration.members;
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)
previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName, isExport);
this.enums.add(name);
previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName);
}
compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null, isExport: bool): string {
const name: string = this.program.mangleInternalName(declaration);
let initializer: BinaryenExpressionRef = declaration.value ? this.compileExpression(<Expression>declaration.value, Type.i32) : 0;
let initializeInStart: bool = declaration.value ? (<Expression>declaration.value).kind != NodeKind.LITERAL : true;
// TODO: WASM does not support binary initializers for globals yet, hence me make them mutable and initialize in start
if (!initializer) {
if (previousName == null) {
initializer = this.module.createI32(0);
initializeInStart = false;
} else {
initializer = this.module.createBinary(BinaryOp.AddI32,
this.module.createGetGlobal(previousName, BinaryenType.I32),
this.module.createI32(1)
);
}
compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null): string {
const internalName: string = declaration.internalName;
// TODO: WASM does not support complex initializers for globals yet, hence we make such globals mutable and initialize in start
let initializer: BinaryenExpressionRef;
let initializeInStart: bool;
if (declaration.value) {
initializer = this.compileExpression(<Expression>declaration.value, Type.i32);
initializeInStart = declaration.value.kind != NodeKind.LITERAL;
} else if (previousName == null) {
initializer = this.module.createI32(0);
initializeInStart = false;
} else {
initializer = this.module.createBinary(BinaryOp.AddI32,
this.module.createGetGlobal(previousName, BinaryenType.I32),
this.module.createI32(1)
);
initializeInStart = true;
}
if (initializeInStart) {
this.module.addGlobal(name, BinaryenType.I32, true, this.module.createI32(-1));
this.startFunctionBody.push(this.module.createSetGlobal(name, initializer));
this.module.addGlobal(internalName, BinaryenType.I32, true, this.module.createI32(-1));
this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else
this.module.addGlobal(name, 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);
return name;
this.module.addGlobal(internalName, BinaryenType.I32, false, initializer);
// export if applicable
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 {
@ -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));
return false;
}
// TODO: check class types, arrays
// TODO: check TypeParameter#extendsType
return true;
}
@ -266,7 +307,7 @@ export class Compiler extends DiagnosticEmitter {
if (!this.checkTypeArguments(declaration.typeParameters, typeArguments, reportNode)) // reports if requested
return;
let globalName: string = this.program.mangleInternalName(declaration);
let internalName: string = declaration.internalName;
// inherit type arguments, i.e. from class
const typeArgumentsMap: Map<string,Type> = new Map();
@ -279,10 +320,10 @@ export class Compiler extends DiagnosticEmitter {
if (k) {
for (i = 0; i < k; ++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)
this.error(DiagnosticCode.Duplicate_function_implementation, (<Node>reportNode).range);
return;
@ -319,96 +360,100 @@ export class Compiler extends DiagnosticEmitter {
// compile statements
const functionType: FunctionType = new FunctionType(typeArguments, parameterTypes, returnType, parameterNames);
this.functions.set(globalName, functionType);
this.functions.set(internalName, functionType);
const previousFunction: FunctionType = this.currentFunction;
this.currentFunction = functionType;
const statements: Statement[] = <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;
const stmts: BinaryenExpressionRef[] = this.compileStatements(<Statement[]>declaration.statements);
this.currentFunction = previousFunction;
if (hasErrors)
return;
// create function
const binaryenResultType: BinaryenType = typeToBinaryenType(returnType);
const binaryenParamTypes: BinaryenType[] = typesToBinaryenTypes(parameterTypes);
let binaryenTypeRef: BinaryenFunctionTypeRef = this.module.getFunctionTypeBySignature(binaryenResultType, binaryenParamTypes);
if (!binaryenTypeRef)
binaryenTypeRef = this.module.addFunctionType(typesToSignatureName(parameterTypes, returnType), binaryenResultType, binaryenParamTypes);
this.module.addFunction(globalName, binaryenTypeRef, typesToBinaryenTypes(functionType.additionalLocals), this.module.createBlock(null, body));
if (declaration.range.source.isEntry && hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.module.addExport(globalName, declaration.identifier.name);
this.module.addFunction(internalName, binaryenTypeRef, typesToBinaryenTypes(functionType.additionalLocals), this.module.createBlock(null, stmts, BinaryenType.None));
// export if applicable
if (declaration.globalExportName != null)
this.module.addExport(internalName, <string>declaration.globalExportName);
}
compileGlobals(statement: VariableStatement): void {
const declarations: VariableDeclaration[] = statement.declarations;
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)
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
if (!type)
return;
const name: string = this.program.mangleInternalName(declaration);
if (this.globals.has(name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name);
const internalName: string = declaration.internalName;
if (this.globals.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
return;
}
const binaryenType: BinaryenType = typeToBinaryenType(<Type>type);
const initializer: BinaryenExpressionRef = declaration.initializer ? this.compileExpression(<Expression>declaration.initializer, <Type>type) : typeToBinaryenZero(this.module, <Type>type);
let initializeInStart: bool = declaration.initializer ? (<Expression>declaration.initializer).kind != NodeKind.LITERAL : false;
// TODO: WASM does not support binary initializers for globals yet, hence me make them mutable and initialize in start
// TODO: WASM does not support complex initializers for globals yet, hence me such globals mutable and initialize in start
let initializer: BinaryenExportRef;
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) {
this.module.addGlobal(name, binaryenType, false, typeToBinaryenZero(this.module, <Type>type));
this.startFunctionBody.push(initializer);
this.module.addGlobal(internalName, binaryenType, true, typeToBinaryenZero(this.module, <Type>type));
this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else
this.module.addGlobal(name, binaryenType, !isConst, initializer);
// TODO: WASM does not support exporting globals yet (the following produces invalid code referencing a non-existent function)
// this.module.addExport(name, declaration.identifier.name);
this.globals.set(name, <Type>type);
this.module.addGlobal(internalName, binaryenType, !isConst, initializer);
this.globals.set(internalName, <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 {
const members: Statement[] = declaration.members;
const noTreeShaking: bool = this.options.noTreeShaking;
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
const member: Statement = members[i];
switch (member.kind) {
case NodeKind.CLASS:
if (!(<ClassDeclaration>member).typeParameters.length)
if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<ClassDeclaration>member).modifiers)) && !(<ClassDeclaration>member).typeParameters.length)
this.compileClass(<ClassDeclaration>member, []);
break;
case NodeKind.ENUM:
this.compileEnum(<EnumDeclaration>member);
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<EnumDeclaration>member).modifiers))
this.compileEnum(<EnumDeclaration>member);
break;
case NodeKind.FUNCTION:
if (!(<FunctionDeclaration>member).typeParameters.length)
if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (<FunctionDeclaration>member).modifiers)) && !(<FunctionDeclaration>member).typeParameters.length)
this.compileFunction(<FunctionDeclaration>member, []);
break;
case NodeKind.NAMESPACE:
this.compileNamespace(<NamespaceDeclaration>member);
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<NamespaceDeclaration>member).modifiers))
this.compileNamespace(<NamespaceDeclaration>member);
break;
case NodeKind.VARIABLE:
this.compileGlobals(<VariableStatement>member);
if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (<VariableStatement>member).modifiers))
this.compileGlobals(<VariableStatement>member);
break;
// TODO: some form of internal visibility?
// case NodeKind.EXPORT:
default:
throw new Error("unexpected namespace member kind");
throw new Error("unexpected namespace member");
}
}
throw new Error("not implemented");
@ -416,14 +461,14 @@ export class Compiler extends DiagnosticEmitter {
compileExports(statement: ExportStatement): void {
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)
this.compileExport(members[i], normalizedPath);
}
compileExport(member: ExportMember, normalizedPath: string | null): void {
const exportName: string = normalizedPath + "/" + member.externalIdentifier.name;
const declaration: DeclarationStatement | null = <DeclarationStatement | null>this.program.exports.get(exportName);
compileExport(member: ExportMember, normalizedPath: string): void {
const internalExportName: string = normalizedPath + "/" + member.identifier.name;
const declaration: DeclarationStatement | null = <DeclarationStatement | null>this.program.exports.get(internalExportName);
if (declaration) {
switch ((<DeclarationStatement>declaration).kind) {
@ -446,7 +491,7 @@ export class Compiler extends DiagnosticEmitter {
break;
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;
default:
@ -554,9 +599,7 @@ export class Compiler extends DiagnosticEmitter {
if (this.functions.has(globalName))
return <FunctionType>this.functions.get(globalName);
this.error(DiagnosticCode.Cannot_find_name_0, expression.range, globalName);
return null;
}
if (expression.kind == NodeKind.PROPERTYACCESS) {
} else {
throw new Error("not implemented");
}
return null;
@ -609,28 +652,36 @@ export class Compiler extends DiagnosticEmitter {
case NodeKind.WHILE:
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 {
const substatements: Statement[] = statement.statements;
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);
return this.module.createBlock(null, this.compileStatements(statement.statements), BinaryenType.None);
}
compileBreakStatement(statement: BreakStatement): BinaryenExpressionRef {
if (statement.label)
throw new Error("not implemented");
const context: string | null = this.currentFunction.breakContext;
if (context)
if (context != null)
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);
return this.module.createUnreachable();
}
compileContinueStatement(statement: ContinueStatement): BinaryenExpressionRef {
if (statement.label)
throw new Error("not implemented");
const context: string | null = this.currentFunction.breakContext;
if (context)
if (context != null && !this.disallowContinue)
return this.module.createBreak("continue$" + (<string>context));
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
return this.module.createUnreachable();
@ -648,8 +699,8 @@ export class Compiler extends DiagnosticEmitter {
this.module.createBlock(null, [
body,
this.module.createBreak(continueLabel, condition)
]))
]);
], BinaryenType.None))
], BinaryenType.None);
}
compileEmptyStatement(statement: EmptyStatement): BinaryenExpressionRef {
@ -676,9 +727,9 @@ export class Compiler extends DiagnosticEmitter {
body,
incrementor,
this.module.createBreak(continueLabel)
]))
]))
]);
], BinaryenType.None))
], BinaryenType.None))
], BinaryenType.None);
}
compileIfStatement(statement: IfStatement): BinaryenExpressionRef {
@ -697,7 +748,57 @@ export class Compiler extends DiagnosticEmitter {
}
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 {
@ -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 {
const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32);
const label: string = this.currentFunction.enterBreakContext();
const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32);
const breakLabel: string = "break$" + label;
const continueLabel: string = "continue$" + label;
const body: BinaryenExpressionRef = this.compileStatement(statement.statement);
this.currentFunction.leaveBreakContext();
return this.module.createBlock(breakLabel, [
this.module.createLoop(continueLabel,
this.module.createIf(condition, this.module.createBlock("", [
this.module.createIf(condition, this.module.createBlock(null, [
body,
this.module.createBreak(continueLabel)
]))
], BinaryenType.None))
)
]);
], BinaryenType.None);
}
// expressions
@ -779,6 +880,10 @@ export class Compiler extends DiagnosticEmitter {
break;
case NodeKind.IDENTIFIER:
case NodeKind.FALSE:
case NodeKind.NULL:
case NodeKind.THIS:
case NodeKind.TRUE:
expr = this.compileIdentifierExpression(<IdentifierExpression>expression, contextualType);
break;
@ -1254,19 +1359,76 @@ export class Compiler extends DiagnosticEmitter {
}
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;
if (locals.has(name)) {
const local: LocalType = <LocalType>locals.get(name);
if (locals.has(globalName)) {
const local: LocalType = <LocalType>locals.get(globalName);
this.currentType = local.type;
return this.module.createGetLocal(local.index, typeToBinaryenType(this.currentType));
}
const globals: Map<string,Type> = this.globals;
if (globals.has(name)) {
this.currentType = <Type>globals.get(name);
return this.module.createGetGlobal(name, typeToBinaryenType(this.currentType));
// global in local file
const localName: string = expression.range.source.normalizedPath + "/" + globalName;
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();
}

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,
Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391,
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 {
@ -109,6 +110,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2391: return "Function implementation is missing or not immediately following the declaration.";
case 2393: return "Duplicate function implementation.";
case 2558: return "Expected {0} type arguments, but got {1}.";
case 6054: return "File '{0}' not found.";
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,
"Function implementation is missing or not immediately following the declaration.": 2391,
"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
|| typeof global !== "undefined" && global
|| self;
const globalScope: any = typeof window !== "undefined" && window || 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;
};
globalScope["load"] = function load_u8(ptr) {
globalScope["load"] = function load_u8(ptr: number) {
return binaryen.HEAPU8[ptr];
};

View File

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

View File

@ -1,6 +1,5 @@
import { Target } from "./compiler";
import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics";
import { hasModifier } from "./parser";
import { Type } from "./types";
import {
@ -24,12 +23,15 @@ import {
NamespaceDeclaration,
Statement,
VariableDeclaration,
VariableStatement
VariableStatement,
hasModifier
} from "./ast";
class QueuedExport {
importName: string;
isForeign: bool;
referencedName: string;
member: ExportMember;
}
@ -47,12 +49,12 @@ export class Program extends DiagnosticEmitter {
diagnosticsOffset: i32 = 0;
target: Target = Target.WASM32;
/** Internal map of names to declarations. */
/** Map of internal names to declarations. */
names: Map<string,DeclarationStatement> = new Map();
/** Separate map of internal type names to declarations. */
types: Map<string,Type> = typesStub;
/** 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) {
super(diagnostics);
@ -81,7 +83,7 @@ export class Program extends DiagnosticEmitter {
const queuedExports: Map<string,QueuedExport> = new Map();
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) {
const source: Source = this.sources[i];
const statements: Statement[] = source.statements;
@ -125,21 +127,36 @@ export class Program extends DiagnosticEmitter {
}
// at this point queued exports should be resolvable
for (let [exportName, queuedExport] of queuedExports.entries()) {
const seen: Set<QueuedExport> = new Set();
while (queuedExports.has(queuedExport.importName)) {
queuedExport = <QueuedExport>queuedExports.get(queuedExport.importName);
if (seen.has(queuedExport))
break;
seen.add(queuedExport);
// export { add }
// export { sub } from "./other"
for (let [exportName, queuedExport] of queuedExports) {
if (queuedExport.isForeign) {
const seen: Set<QueuedExport> = new Set();
while (queuedExports.has(queuedExport.referencedName)) {
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) {
const queuedImport: QueuedImport = queuedImports[i];
const internalName: string = queuedImport.internalName;
@ -147,7 +164,7 @@ export class Program extends DiagnosticEmitter {
let importName: string = queuedImport.importName;
while (queuedExports.has(importName)) {
const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(importName);
importName = queuedExport.importName;
importName = queuedExport.referencedName;
if (seen.has(queuedExport))
break;
seen.add(queuedExport);
@ -167,7 +184,7 @@ export class Program extends DiagnosticEmitter {
}
private initializeClass(declaration: ClassDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration);
@ -191,12 +208,11 @@ export class Program extends DiagnosticEmitter {
}
private initializeField(declaration: FieldDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
this.addName(internalName, declaration);
this.addName(declaration.internalName, declaration);
}
private initializeEnum(declaration: EnumDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration);
@ -206,8 +222,7 @@ export class Program extends DiagnosticEmitter {
}
private initializeEnumValue(declaration: EnumValueDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
this.addName(internalName, declaration);
this.addName(declaration.internalName, declaration);
}
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);
else {
const queuedExport: QueuedExport = new QueuedExport();
queuedExport.importName = normalizedPath == null
? member.range.source.normalizedPath + "/" + member.identifier.name // local
: (<string>normalizedPath) + "/" + member.identifier.name; // re-export
if (normalizedPath == null) {
queuedExport.isForeign = false;
queuedExport.referencedName = member.range.source.normalizedPath + "/" + member.identifier.name;
} else {
queuedExport.isForeign = true;
queuedExport.referencedName = (<string>normalizedPath) + "/" + member.identifier.name;
}
queuedExport.member = member;
queuedExports.set(exportName, queuedExport);
}
}
private initializeFunction(declaration: FunctionDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration);
@ -251,12 +270,12 @@ export class Program extends DiagnosticEmitter {
const seen: Set<QueuedExport> = new Set();
while (queuedExports.has(resolvedImportName)) {
const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(resolvedImportName);
resolvedImportName = queuedExport.importName;
resolvedImportName = queuedExport.referencedName;
if (seen.has(queuedExport))
break;
seen.add(queuedExport);
}
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
if (this.exports.has(resolvedImportName)) { // resolvable right away
if (this.names.has(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 {
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration);
@ -296,12 +315,11 @@ export class Program extends DiagnosticEmitter {
}
private initializeMethod(declaration: MethodDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
this.addName(internalName, declaration);
this.addName(declaration.internalName, declaration);
}
private initializeNamespace(declaration: NamespaceDeclaration): void {
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
this.addName(internalName, declaration);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers))
this.addExport(/* same as */internalName, declaration);
@ -345,7 +363,7 @@ export class Program extends DiagnosticEmitter {
const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers);
for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) {
const declaration: VariableDeclaration = declarations[i];
const internalName: string = this.mangleInternalName(declaration);
const internalName: string = declaration.internalName;
this.addName(internalName, declaration);
if (isExport)
this.addExport(/* same as */internalName, declaration);
@ -362,51 +380,10 @@ export class Program extends DiagnosticEmitter {
private addExport(exportName: string, declaration: DeclarationStatement): void {
if (this.exports.has(exportName))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, exportName); // recoverable
else
else {
this.exports.set(exportName, declaration);
}
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;
}
}
if (declaration.range.source.isEntry)
declaration.globalExportName = declaration.identifier.name;
}
throw new Error("unexpected parent");
}
}

View File

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

View File

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

View File

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