diff --git a/.gitignore b/.gitignore index 1ee5e907..4e933ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ node_modules/ npm-debug.* out/ raw/ - diff --git a/src/ast.ts b/src/ast.ts index 4ed66e61..12ca05c9 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -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(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((this.label).name); + (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 = (this.modifiers).length; i < k; ++i) { + (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); + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (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 = (this.modifiers).length; i < k; ++i) { + (this.modifiers)[i].serialize(sb); + sb.push(" "); + if ((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 = (modifiers).length; i < k; ++i) + if ((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 (declaration.parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? "." : "#") + name; + if (declaration.parent.kind == NodeKind.NAMESPACE || declaration.parent.kind == NodeKind.ENUM) + return (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]; diff --git a/src/binaryen.ts b/src/binaryen.ts index f42f8cbc..726edb15 100644 --- a/src/binaryen.ts +++ b/src/binaryen.ts @@ -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 = 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 diff --git a/src/compiler.ts b/src/compiler.ts index 69a02631..8d5d96a8 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -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 = new Set(); classes: Map = new Map(); enums: Set = new Set(); functions: Map = 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, (statement).modifiers) && !(statement).typeParameters.length) - this.compileClass(statement, []); - break; - - case NodeKind.ENUM: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) - this.compileEnum(statement); - break; - - case NodeKind.FUNCTION: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers) && !(statement).typeParameters.length) - this.compileFunction(statement, []); - break; - - case NodeKind.IMPORT: - break; - - case NodeKind.NAMESPACE: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) - this.compileNamespace(statement); - break; - - case NodeKind.VARIABLE: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) - this.compileGlobals(statement); - break; - - case NodeKind.EXPORT: { - this.compileExports(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, (statement).modifiers)) && !(statement).typeParameters.length) + this.compileClass(statement, []); + break; + + case NodeKind.ENUM: + if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) + this.compileEnum(statement); + break; + + case NodeKind.FUNCTION: + if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) && !(statement).typeParameters.length) + this.compileFunction(statement, []); + break; + + case NodeKind.IMPORT: + this.compileFileByPath((statement).normalizedPath, (statement).path); + break; + + case NodeKind.NAMESPACE: + if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) + this.compileNamespace(statement); + break; + + case NodeKind.VARIABLE: + if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) + this.compileGlobals(statement); + break; + + case NodeKind.EXPORT: + if ((statement).path) + this.compileFileByPath(((statement).path).value, (statement).path); + if (noTreeShaking || isEntry) + this.compileExports(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(declaration.value, Type.i32) : 0; - let initializeInStart: bool = declaration.value ? (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(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, (declaration.parent).identifier.name + "." + declaration.identifier.name); - return name; + this.module.addGlobal(internalName, BinaryenType.I32, false, initializer); + + // export if applicable + if (declaration.parent && (declaration.parent).kind == NodeKind.ENUM && (declaration.parent).globalExportName != null) { + // TODO: WASM does not support exporting globals yet + // this.module.addExport(internalName, (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, (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 = 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, (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[] = 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(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, 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(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); - const initializer: BinaryenExpressionRef = declaration.initializer ? this.compileExpression(declaration.initializer, type) : typeToBinaryenZero(this.module, type); - let initializeInStart: bool = declaration.initializer ? (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(declaration.initializer, type); + initializeInStart = (declaration.initializer).kind != NodeKind.LITERAL; + } else { + initializer = typeToBinaryenZero(this.module, type); + initializeInStart = false; + } if (initializeInStart) { - this.module.addGlobal(name, binaryenType, false, typeToBinaryenZero(this.module, type)); - this.startFunctionBody.push(initializer); + this.module.addGlobal(internalName, binaryenType, true, typeToBinaryenZero(this.module, 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); + this.module.addGlobal(internalName, binaryenType, !isConst, initializer); + this.globals.set(internalName, type); + + // export if applicable + if (declaration.globalExportName != null) { + // TODO: WASM does not support exporting globals yet + // this.module.addExport(internalName, 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 (!(member).typeParameters.length) + if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) && !(member).typeParameters.length) this.compileClass(member, []); break; case NodeKind.ENUM: - this.compileEnum(member); + if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) + this.compileEnum(member); break; case NodeKind.FUNCTION: - if (!(member).typeParameters.length) + if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) && !(member).typeParameters.length) this.compileFunction(member, []); break; case NodeKind.NAMESPACE: - this.compileNamespace(member); + if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) + this.compileNamespace(member); break; case NodeKind.VARIABLE: - this.compileGlobals(member); + if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) + this.compileGlobals(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(statement.path); + const normalizedPath: string | null = statement.path ? normalizePath((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 = this.program.exports.get(exportName); + compileExport(member: ExportMember, normalizedPath: string): void { + const internalExportName: string = normalizedPath + "/" + member.identifier.name; + const declaration: DeclarationStatement | null = this.program.exports.get(internalExportName); if (declaration) { switch ((declaration).kind) { @@ -446,7 +491,7 @@ export class Compiler extends DiagnosticEmitter { break; case NodeKind.VARIABLEDECLARATION: - this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, (declaration.parent).modifiers), member.range.source.isEntry); + this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, (declaration.parent).modifiers)); break; default: @@ -554,9 +599,7 @@ export class Compiler extends DiagnosticEmitter { if (this.functions.has(globalName)) return 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(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$" + (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$" + (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(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 = this.currentFunction.locals; - if (locals.has(name)) { - const local: LocalType = locals.get(name); + if (locals.has(globalName)) { + const local: LocalType = locals.get(globalName); this.currentType = local.type; return this.module.createGetLocal(local.index, typeToBinaryenType(this.currentType)); } - const globals: Map = this.globals; - if (globals.has(name)) { - this.currentType = 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 = this.program.names.get(localName); + if (declaration.kind == NodeKind.VARIABLEDECLARATION) { + if (!this.globals.has(declaration.internalName)) + this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, declaration.modifiers)); + } + + // global across files + } else if (this.program.names.has(globalName)) { + const declaration: DeclarationStatement = this.program.names.get(globalName); + if (declaration.kind == NodeKind.VARIABLEDECLARATION) { + if (!this.globals.has(declaration.internalName)) + this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, declaration.modifiers)); + determinedName = globalName; + } } - this.error(DiagnosticCode.Cannot_find_name_0, expression.range, name); + + const globals: Map = this.globals; + if (globals.has(determinedName)) { + this.currentType = 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(); } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 7ca66c7e..f80e053c 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -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 ""; } } diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 249fd24b..a98e2cd3 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -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 } diff --git a/src/glue/js.ts b/src/glue/js.ts index 05a1fd76..dec17c2a 100644 --- a/src/glue/js.ts +++ b/src/glue/js.ts @@ -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]; }; diff --git a/src/parser.ts b/src/parser.ts index 7984bfe3..77844e22 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -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, parameters, returnType, statements, decorators ? decorators : new Array(0), tn.range(startPos, tn.pos)); + const ret: FunctionDeclaration = Statement.createFunction(modifiers, identifier, typeParameters, parameters, returnType, statements, decorators ? 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(member); } while (!tn.skip(Token.CLOSEBRACE)); } - return Statement.createClass(modifiers, identifier, typeParameters, extendsType, implementsTypes, members, decorators ? decorators : new Array(0), tn.range(startPos, tn.pos)); + return Statement.createClass(modifiers, identifier, typeParameters, extendsType, implementsTypes, members, decorators ? 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 ((operand).kind != NodeKind.IDENTIFIER && (operand).kind != NodeKind.ELEMENTACCESS && (operand).kind != NodeKind.PROPERTYACCESS) - this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, (operand).range); + if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) + if ((operand).kind != NodeKind.IDENTIFIER && (operand).kind != NodeKind.ELEMENTACCESS && (operand).kind != NodeKind.PROPERTYACCESS) + this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, (operand).range); return Expression.createUnaryPrefix(token, 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 ? typeArguments : new Array(0), args, tn.range(startPos, tn.pos)); + expr = Expression.createCall(expr, (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) diff --git a/src/program.ts b/src/program.ts index 07a59a76..d3603746 100644 --- a/src/program.ts +++ b/src/program.ts @@ -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 = new Map(); /** Separate map of internal type names to declarations. */ types: Map = typesStub; /** Separate map of internal export names to declarations. */ - exports: Map = new Map(); + exports: Map = new Map(); // not global exports constructor(diagnostics: DiagnosticMessage[] | null = null) { super(diagnostics); @@ -81,7 +83,7 @@ export class Program extends DiagnosticEmitter { const queuedExports: Map = 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 = new Set(); - while (queuedExports.has(queuedExport.importName)) { - 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 = new Set(); + while (queuedExports.has(queuedExport.referencedName)) { + queuedExport = queuedExports.get(queuedExport.referencedName); + if (seen.has(queuedExport)) + break; + seen.add(queuedExport); + } + if (this.exports.has(queuedExport.referencedName)) { + const declaration: 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 = 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, 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 = 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): 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 - : (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 = (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 = new Set(); while (queuedExports.has(resolvedImportName)) { const 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 (parent).normalizedPath + "/" + name; - - case NodeKind.CLASS: { - if ( - (declaration.kind == NodeKind.FIELD && !hasModifier(ModifierKind.STATIC, (declaration).modifiers)) || - (declaration.kind == NodeKind.METHOD && !hasModifier(ModifierKind.STATIC, (declaration).modifiers)) - ) - return this.mangleInternalName(parent) + "#" + name; - // otherwise fall through - } - case NodeKind.ENUM: - case NodeKind.ENUMVALUE: - case NodeKind.NAMESPACE: - return this.mangleInternalName(parent) + "." + name; - - case NodeKind.IMPORT: { - const importParent: Node | null = (parent).parent; - if (importParent && importParent.kind == NodeKind.SOURCE) - return (importParent).path + "/" + name; - break; - } - - case NodeKind.VARIABLE: { - const variableParent: Node | null = (parent).parent; - if (variableParent) { - if (variableParent.kind == NodeKind.SOURCE) - return variableParent == this.sources[0] ? name : (variableParent).path + "/" + name; - if (variableParent.kind == NodeKind.NAMESPACE) - return this.mangleInternalName(variableParent) + "." + name; - } - break; - } - } + if (declaration.range.source.isEntry) + declaration.globalExportName = declaration.identifier.name; } - throw new Error("unexpected parent"); } } diff --git a/src/tsconfig.json b/src/tsconfig.json index b004e967..1f3f7f5c 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -13,6 +13,9 @@ "experimentalDecorators": true, "strictNullChecks": true, "alwaysStrict": true, + "noImplicitReturns": true, + "noImplicitAny": true, + "noImplicitThis": true, "outDir": "../out" }, "files": [ diff --git a/tests/binaryen.ts b/tests/binaryen.ts index a0e69e09..9a502693 100644 --- a/tests/binaryen.ts +++ b/tests/binaryen.ts @@ -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"); diff --git a/tests/compiler.ts b/tests/compiler.ts index 3d8c94ce..463153c9 100644 --- a/tests/compiler.ts +++ b/tests/compiler.ts @@ -11,12 +11,37 @@ import { Parser } from "../src/parser"; ]); */ const files: Map = 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(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); -}); */ \ No newline at end of file