diff --git a/src/ast.ts b/src/ast.ts index b76b9ac6..4ed66e61 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -94,11 +94,16 @@ export enum NodeKind { BINARY, CALL, ELEMENTACCESS, + FALSE, LITERAL, NEW, + NULL, PARENTHESIZED, PROPERTYACCESS, SELECT, + SUPER, + THIS, + TRUE, UNARYPOSTFIX, UNARYPREFIX, @@ -163,6 +168,7 @@ export function nodeKindToString(kind: NodeKind): string { case NodeKind.EXPORTIMPORT: return "EXPORTIMPORT"; case NodeKind.EXPRESSION: return "EXPRESSION"; case NodeKind.INTERFACE: return "INTERFACE"; + case NodeKind.FALSE: return "FALSE"; case NodeKind.FOR: return "FOR"; case NodeKind.FUNCTION: return "FUNCTION"; case NodeKind.IF: return "IF"; @@ -170,10 +176,14 @@ export function nodeKindToString(kind: NodeKind): string { case NodeKind.IMPORTDECLARATION: return "IMPORTDECLARATION"; case NodeKind.METHOD: return "METHOD"; case NodeKind.NAMESPACE: return "NAMESPACE"; + case NodeKind.NULL: return "NULL"; case NodeKind.FIELD: return "PROPERTY"; case NodeKind.RETURN: return "RETURN"; + case NodeKind.SUPER: return "SUPER"; case NodeKind.SWITCH: return "SWITCH"; + case NodeKind.THIS: return "THIS"; case NodeKind.THROW: return "THROW"; + case NodeKind.TRUE: return "TRUE"; case NodeKind.TRY: return "TRY"; case NodeKind.VARIABLE: return "VARIABLE"; case NodeKind.VARIABLEDECLARATION: return "VARIABLEDECLARATION"; @@ -286,6 +296,12 @@ export abstract class Expression extends Node { return expr; } + static createFalse(range: Range): FalseExpression { + const expr: FalseExpression = new FalseExpression(); + expr.range = range; + return expr; + } + static createFloatLiteral(value: f64, range: Range): FloatLiteralExpression { const expr: FloatLiteralExpression = new FloatLiteralExpression(); expr.range = range; @@ -310,6 +326,12 @@ export abstract class Expression extends Node { return expr; } + static createNull(range: Range): NullExpression { + const expr: NullExpression = new NullExpression(); + expr.range = range; + return expr; + } + static createParenthesized(expression: Expression, range: Range): ParenthesizedExpression { const expr: ParenthesizedExpression = new ParenthesizedExpression(); expr.range = range; @@ -348,6 +370,24 @@ export abstract class Expression extends Node { return expr; } + static createSuper(range: Range): SuperExpression { + const expr: SuperExpression = new SuperExpression(); + expr.range = range; + return expr; + } + + static createThis(range: Range): ThisExpression { + const expr: ThisExpression = new ThisExpression(); + expr.range = range; + return expr; + } + + static createTrue(range: Range): TrueExpression { + const expr: TrueExpression = new TrueExpression(); + expr.range = range; + return expr; + } + static createUnaryPostfix(operator: Token, expression: Expression, range: Range): UnaryPostfixExpression { const expr: UnaryPostfixExpression = new UnaryPostfixExpression(); expr.range = range; @@ -614,6 +654,11 @@ export class NewExpression extends CallExpression { } } +export class NullExpression extends IdentifierExpression { + kind = NodeKind.NULL; + name = "null"; +} + export class ParenthesizedExpression extends Expression { kind = NodeKind.PARENTHESIZED; @@ -711,6 +756,26 @@ export class StringLiteralExpression extends LiteralExpression { } } +export class SuperExpression extends IdentifierExpression { + kind = NodeKind.SUPER; + name = "super"; +} + +export class ThisExpression extends IdentifierExpression { + kind = NodeKind.THIS; + name = "this"; +} + +export class TrueExpression extends IdentifierExpression { + kind = NodeKind.TRUE; + name = "true"; +} + +export class FalseExpression extends IdentifierExpression { + kind = NodeKind.FALSE; + name = "false"; +} + export abstract class UnaryExpression extends Expression { operator: Token; expression: Expression; @@ -798,7 +863,7 @@ export abstract class Statement extends Node { static createBlock(statements: Statement[], range: Range): BlockStatement { const stmt: BlockStatement = new BlockStatement(); stmt.range = range; - for (let i: i32 = 0, k = (stmt.statements = statements).length; i < k; ++i) statements[i].parent = stmt; + for (let i: i32 = 0, k: i32 = (stmt.statements = statements).length; i < k; ++i) statements[i].parent = stmt; return stmt; } @@ -1055,13 +1120,14 @@ export abstract class Statement extends Node { return stmt; } - static createTry(statements: Statement[], catchVariable: VariableDeclaration, catchStatements: Statement[], range: Range): TryStatement { + static createTry(statements: Statement[], catchVariable: IdentifierExpression | null, catchStatements: Statement[] | null, finallyStatements: Statement[] | null, range: Range): TryStatement { const stmt: TryStatement = new TryStatement(); stmt.range = range; let i: i32, k: i32; for (i = 0, k = (stmt.statements = statements).length; i < k; ++i) statements[i].parent = stmt; - (stmt.catchVariable = catchVariable).parent = stmt; - for (i = 0, k = (stmt.catchStatements = catchStatements).length; i < k; ++i) catchStatements[i].parent = stmt; + if (stmt.catchVariable = catchVariable) (catchVariable).parent = stmt; + if (stmt.catchStatements = catchStatements) for (i = 0, k = (catchStatements).length; i < k; ++i) (catchStatements)[i].parent = stmt; + if (stmt.finallyStatements = finallyStatements) for (i = 0, k = (finallyStatements).length; i < k; ++i) (finallyStatements)[i].parent = stmt; return stmt; } @@ -1117,7 +1183,7 @@ export class Source extends Node { get isDeclaration(): bool { return !this.isEntry && this.path.endsWith(".d.ts"); } serialize(sb: string[]): void { - for (let i: i32 = 0, k = this.statements.length; i < k; ++i) { + for (let i: i32 = 0, k: i32 = this.statements.length; i < k; ++i) { const statement: Statement = this.statements[i]; statement.serialize(sb); const last: string = sb[sb.length - 1]; @@ -1142,7 +1208,7 @@ export class BlockStatement extends Statement { sb.push("{\n"); for (let i: i32 = 0, k: i32 = this.statements.length; i < k; ++i) { this.statements[i].serialize(sb); - if (builderEndsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(sb, CharCode.CLOSEBRACE)) sb.push("\n"); else sb.push(";\n"); @@ -1211,7 +1277,7 @@ export class ClassDeclaration extends DeclarationStatement { sb.push(" {\n"); for (i = 0, k = this.members.length; i < k; ++i) { this.members[i].serialize(sb); - if (builderEndsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(sb, CharCode.CLOSEBRACE)) sb.push("\n"); else sb.push(";\n"); @@ -1493,7 +1559,7 @@ export class FunctionDeclaration extends DeclarationStatement { for (i = 0, k = (this.statements).length; i < k; ++i) { const statement: Statement = (this.statements)[i]; statement.serialize(sb); - if (builderEndsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(sb, CharCode.CLOSEBRACE)) sb.push("\n"); else sb.push(";\n"); @@ -1592,7 +1658,7 @@ export class InterfaceDeclaration extends DeclarationStatement { sb.push(" {\n"); for (i = 0, k = this.members.length; i < k; ++i) { this.members[i].serialize(sb); - if (builderEndsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(sb, CharCode.CLOSEBRACE)) sb.push("\n"); else sb.push(";\n"); @@ -1623,7 +1689,7 @@ export class NamespaceDeclaration extends DeclarationStatement { kind = NodeKind.NAMESPACE; modifiers: Modifier[]; - members: DeclarationStatement[]; + members: Statement[]; serialize(sb: string[]): void { let i: i32, k: i32; @@ -1636,7 +1702,7 @@ export class NamespaceDeclaration extends DeclarationStatement { sb.push(" {\n"); for (i = 0, k = this.members.length; i < k; ++i) { this.members[i].serialize(sb); - if (builderEndsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(sb, CharCode.CLOSEBRACE)) sb.push("\n"); else sb.push(";\n"); @@ -1720,7 +1786,7 @@ export class SwitchCase extends Node { if (i > 0) sb.push("\n"); this.statements[i].serialize(sb); - if (builderEndsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(sb, CharCode.CLOSEBRACE)) sb.push("\n"); else sb.push(";\n"); @@ -1761,8 +1827,9 @@ export class TryStatement extends Statement { kind = NodeKind.TRY; statements: Statement[]; - catchVariable: VariableDeclaration; - catchStatements: Statement[]; + catchVariable: IdentifierExpression | null; + catchStatements: Statement[] | null; + finallyStatements: Statement[] | null; serialize(sb: string[]): void { sb.push("try {\n"); @@ -1771,12 +1838,22 @@ export class TryStatement extends Statement { this.statements[i].serialize(sb); sb.push(";\n"); } - sb.push("} catch ("); - this.catchVariable.serialize(sb); - sb.push(") {\n"); - for (i = 0, k = this.catchStatements.length; i < k; ++i) { - this.catchStatements[i].serialize(sb); - sb.push(";\n"); + if (this.catchVariable) { + sb.push("} catch ("); + (this.catchVariable).serialize(sb); + sb.push(") {\n"); + if (this.catchStatements) + for (i = 0, k = (this.catchStatements).length; i < k; ++i) { + (this.catchStatements)[i].serialize(sb); + sb.push(";\n"); + } + } + if (this.finallyStatements) { + sb.push("} finally {\n"); + for (i = 0, k = (this.finallyStatements).length; i < k; ++i) { + (this.finallyStatements)[i].serialize(sb); + sb.push(";\n"); + } } sb.push("}"); } @@ -1857,7 +1934,7 @@ export function serialize(node: Node, indent: i32 = 0): string { return sb.join(""); } -function builderEndsWith(code: CharCode, sb: string[]): bool { +function builderEndsWith(sb: string[], code: CharCode): bool { if (sb.length) { const last: string = sb[sb.length - 1]; return last.length ? last.charCodeAt(last.length - 1) == code : false; diff --git a/src/binaryen.ts b/src/binaryen.ts index f8899585..f42f8cbc 100644 --- a/src/binaryen.ts +++ b/src/binaryen.ts @@ -172,11 +172,15 @@ export class Module { ref: BinaryenModuleRef; lit: BinaryenLiteral; + noEmit: bool; + + static MAX_MEMORY_WASM32: BinaryenIndex = 0xffff; static create(): Module { const module: Module = new Module(); module.ref = _BinaryenModuleCreate(); module.lit = _malloc(16); + module.noEmit = false; return module; } @@ -186,17 +190,27 @@ export class Module { const module: Module = new Module(); module.ref = _BinaryenModuleRead(cArr, buffer.length); module.lit = _malloc(16); + module.noEmit = false; return module; } finally { _free(cArr); } } - static MAX_MEMORY_WASM32: BinaryenIndex = 0xffff; + static createStub(): Module { + const module: Module = new Module(); + module.ref = 0; + module.lit = 0; + module.noEmit = true; + return module; + } + + private constructor() { } // types addFunctionType(name: string, result: Type, paramTypes: Type[]): BinaryenFunctionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(name); const cArr: CArray = allocI32Array(paramTypes); try { @@ -208,6 +222,7 @@ export class Module { } getFunctionTypeBySignature(result: Type, paramTypes: Type[]): BinaryenFunctionTypeRef { + if (this.noEmit) return 0; const cArr: CArray = allocI32Array(paramTypes); try { return _BinaryenGetFunctionTypeBySignature(this.ref, result, cArr, paramTypes.length); @@ -219,34 +234,41 @@ export class Module { // expressions createI32(value: i32): BinaryenExpressionRef { + if (this.noEmit) return 0; _BinaryenLiteralInt32(this.lit, value); return _BinaryenConst(this.ref, this.lit); } createI64(lo: i32, hi: i32): BinaryenExpressionRef { + if (this.noEmit) return 0; _BinaryenLiteralInt64(this.lit, lo, hi); return _BinaryenConst(this.ref, this.lit); } createF32(value: f32): BinaryenExpressionRef { + if (this.noEmit) return 0; _BinaryenLiteralFloat32(this.lit, value); return _BinaryenConst(this.ref, this.lit); } createF64(value: f64): BinaryenExpressionRef { + if (this.noEmit) return 0; _BinaryenLiteralFloat64(this.lit, value); return _BinaryenConst(this.ref, this.lit); } createUnary(op: UnaryOp, expr: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenUnary(this.ref, op, expr); } createBinary(op: BinaryOp, left: BinaryenExpressionRef, right: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenBinary(this.ref, op, left, right); } createHost(op: HostOp, name: string | null = null, operands: BinaryenExpressionRef[] | null = null): BinaryenExpressionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(name); const cArr: CArray = allocI32Array(operands); try { @@ -258,14 +280,17 @@ export class Module { } createGetLocal(index: i32, type: Type): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenGetLocal(this.ref, index, type); } createTeeLocal(index: i32, value: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenTeeLocal(this.ref, index, value); } createGetGlobal(name: string, type: Type): BinaryenExpressionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(name); try { return _BinaryenGetGlobal(this.ref, cStr, type); @@ -274,21 +299,15 @@ export class Module { } } - createTeeGlobal(name: string, value: BinaryenExpressionRef, type: Type): BinaryenExpressionRef { - // emulated, lives here for simplicity reasons - return this.createBlock(null, [ - this.createSetGlobal(name, value), - this.createGetGlobal(name, type) - ], type); - } - // statements createSetLocal(index: i32, value: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenSetLocal(this.ref, index, value); } createSetGlobal(name: string, value: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(name); try { return _BinaryenSetGlobal(this.ref, cStr, value); @@ -298,6 +317,7 @@ export class Module { } createBlock(label: string | null, children: BinaryenExpressionRef[], type: Type = Type.Undefined): BinaryenExpressionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(label); const cArr: CArray = allocI32Array(children); try { @@ -309,6 +329,7 @@ export class Module { } createBreak(label: string | null, condition: BinaryenExpressionRef = 0, value: BinaryenExpressionRef = 0): BinaryenExpressionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(label); try { return _BinaryenBreak(this.ref, cStr, condition, value); @@ -318,10 +339,12 @@ export class Module { } createDrop(expression: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenDrop(this.ref, expression); } createLoop(label: string | null, body: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(label); try { return _BinaryenLoop(this.ref, cStr, body); @@ -331,22 +354,27 @@ export class Module { } createIf(condition: BinaryenExpressionRef, ifTrue: BinaryenExpressionRef, ifFalse: BinaryenExpressionRef = 0): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenIf(this.ref, condition, ifTrue, ifFalse); } createNop(): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenNop(this.ref); } createReturn(expression: BinaryenExpressionRef = 0): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenReturn(this.ref, expression); } createSelect(condition: BinaryenExpressionRef, ifTrue: BinaryenExpressionRef, ifFalse: BinaryenExpressionRef): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenSelect(this.ref, condition, ifTrue, ifFalse); } createSwitch(names: string[], defaultName: string | null, condition: BinaryenExpressionRef, value: BinaryenExpressionRef = 0): BinaryenExpressionRef { + if (this.noEmit) return 0; const strs: CString[] = new Array(names.length); let i: i32, k: i32 = names.length; for (i = 0; i < k; ++i) strs[i] = allocString(names[i]); @@ -362,6 +390,7 @@ export class Module { } createCall(target: BinaryenFunctionRef, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { + if (this.noEmit) return 0; const cArr: CArray = allocI32Array(operands); try { return _BinaryenCall(this.ref, target, cArr, operands.length, returnType); @@ -371,6 +400,7 @@ export class Module { } createCallImport(target: BinaryenImportRef, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { + if (this.noEmit) return 0; const cArr: CArray = allocI32Array(operands); try { return _BinaryenCallImport(this.ref, target, cArr, operands.length, returnType); @@ -380,12 +410,14 @@ export class Module { } createUnreachable(): BinaryenExpressionRef { + if (this.noEmit) return 0; return _BinaryenUnreachable(this.ref); } // meta addGlobal(name: string, type: Type, mutable: bool, initializer: BinaryenExpressionRef): BinaryenImportRef { + if (this.noEmit) return 0; const cStr: CString = allocString(name); try { return _BinaryenAddGlobal(this.ref, cStr, type, mutable ? 1 : 0, initializer); @@ -395,6 +427,7 @@ export class Module { } addFunction(name: string, type: BinaryenFunctionTypeRef, varTypes: Type[], body: BinaryenExpressionRef): BinaryenFunctionRef { + if (this.noEmit) return 0; const cStr: CString = allocString(name); const cArr: CArray = allocI32Array(varTypes); try { @@ -406,6 +439,7 @@ export class Module { } addExport(internalName: string, externalName: string): BinaryenExportRef { + if (this.noEmit) return 0; const cStr1: CString = allocString(internalName); const cStr2: CString = allocString(externalName); try { @@ -417,6 +451,7 @@ export class Module { } removeExport(externalName: string): void { + if (this.noEmit) return; const cStr = allocString(externalName); try { _BinaryenRemoveExport(this.ref, cStr); @@ -426,6 +461,7 @@ export class Module { } addImport(internalName: string, externalModuleName: string, externalBaseName: string, type: BinaryenFunctionTypeRef): BinaryenImportRef { + if (this.noEmit) return 0; const cStr1: CString = allocString(internalName); const cStr2: CString = allocString(externalModuleName); const cStr3: CString = allocString(externalBaseName); @@ -439,6 +475,7 @@ export class Module { } removeImport(internalName: string): void { + if (this.noEmit) return; const cStr: CString = allocString(internalName); try { _BinaryenRemoveImport(this.ref, cStr); @@ -448,6 +485,7 @@ export class Module { } setMemory(initial: BinaryenIndex, maximum: BinaryenIndex, segments: MemorySegment[], target: Target, exportName: string | null = null): void { + if (this.noEmit) return; const cStr: CString = allocString(exportName); let i: i32, k: i32 = segments.length; const segs: CArray[] = new Array(k); @@ -477,18 +515,22 @@ export class Module { } setStart(func: BinaryenFunctionRef): void { + if (this.noEmit) return; _BinaryenSetStart(this.ref, func); } optimize(): void { + if (this.noEmit) return; _BinaryenModuleOptimize(this.ref); } validate(): bool { + if (this.noEmit) return false; return _BinaryenModuleValidate(this.ref) == 1; } dispose(): void { + if (!this.ref) return; // sic _BinaryenModuleDispose(this.ref); _free(this.lit); } @@ -547,7 +589,7 @@ function allocString(str: string | null): CString { if (!str) return 0; const ptr: usize = _malloc(stringLengthUTF8((str)) + 1); let idx: usize = ptr; - for (let i: i32 = 0, k = (str).length; i < k; ++i) { + for (let i: i32 = 0, k: i32 = (str).length; i < k; ++i) { let u: i32 = (str).charCodeAt(i); if (u >= 0xD800 && u <= 0xDFFF && i + 1 < k) u = 0x10000 + ((u & 0x3FF) << 10) | ((str).charCodeAt(++i) & 0x3FF); diff --git a/src/compiler.ts b/src/compiler.ts index 9b95c492..69a02631 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6,8 +6,10 @@ import { CharCode, I64, U64, normalizePath, sb } from "./util"; import { Token } from "./tokenizer"; import { + Node, NodeKind, TypeNode, + TypeParameter, Source, // statements @@ -65,8 +67,10 @@ import { ClassType, FunctionType, + LocalType, Type, - TypeKind + TypeKind, + typeArgumentsToString } from "./types"; @@ -80,13 +84,15 @@ export class Options { noEmit: bool = false; } +const VOID: BinaryenExpressionRef = 0; + export class Compiler extends DiagnosticEmitter { program: Program; options: Options; module: Module; - startFunction: FunctionType = new FunctionType(Type.void, new Array()); + startFunction: FunctionType = new FunctionType([], [], Type.void); startFunctionBody: BinaryenExpressionRef[] = new Array(); currentType: Type = Type.void; @@ -99,7 +105,7 @@ export class Compiler extends DiagnosticEmitter { classes: Map = new Map(); enums: Set = new Set(); functions: Map = new Map(); - globals: Set = new Set(); + globals: Map = new Map(); static compile(program: Program, options: Options | null = null): Module { const compiler: Compiler = new Compiler(program, options); @@ -110,7 +116,8 @@ export class Compiler extends DiagnosticEmitter { super(program.diagnostics); this.program = program; this.options = options ? options : new Options(); - this.module = Module.create(); + this.module = this.options.noEmit ? Module.createStub() : Module.create(); + // noEmit performs compilation as usual but all binaryen methods are nops instead } compile(): Module { @@ -121,7 +128,7 @@ export class Compiler extends DiagnosticEmitter { // start by compiling entry file exports const entrySource: Source = program.sources[0]; - for (let i: i32 = 0, k = entrySource.statements.length; i < k; ++i) { + for (let i: i32 = 0, k: i32 = entrySource.statements.length; i < k; ++i) { const statement: Statement = entrySource.statements[i]; switch (statement.kind) { @@ -177,7 +184,7 @@ export class Compiler extends DiagnosticEmitter { if (!typeRef) typeRef = this.module.addFunctionType("v", BinaryenType.None, []); this.module.setStart( - this.module.addFunction(".start", typeRef, [], this.module.createBlock("", this.startFunctionBody)) + this.module.addFunction("", typeRef, typesToBinaryenTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) ); } @@ -207,43 +214,146 @@ export class Compiler extends DiagnosticEmitter { } 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); + previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName, isExport); this.enums.add(name); } - compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null): string { + 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; - if (!this.options.noEmit) { - if (!initializer) initializer = previousName == null - ? this.module.createI32(0) - : this.module.createBinary(BinaryOp.AddI32, - this.module.createGetGlobal(previousName, BinaryenType.I32), - this.module.createI32(1) - ); - this.module.addGlobal(name, BinaryenType.I32, false, initializer); + 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) + ); + } } + if (initializeInStart) { + this.module.addGlobal(name, BinaryenType.I32, true, this.module.createI32(-1)); + this.startFunctionBody.push(this.module.createSetGlobal(name, 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; } - compileFunction(declaration: FunctionDeclaration, typeArguments: Type[]): void { - /* const binaryenResultType: BinaryenType = declaration.returnType ? this.resolveType(declaration.returnType, true) : BinaryenType.None; - const binaryenParamTypes: BinaryenType[] = new Array(); + checkTypeArguments(typeParameters: TypeParameter[], typeArguments: Type[], reportNode: Node | null = null): bool { + if (typeParameters.length != typeArguments.length) { + if (reportNode) + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, (reportNode).range, typeParameters.length.toString(10), typeArguments.length.toString(10)); + return false; + } + // TODO: check TypeParameter#extendsType + return true; + } + + compileFunction(declaration: FunctionDeclaration, typeArguments: Type[], inheritedTypeArgumentsMap: Map | null = null, reportNode: Node | null = null): void { + if (!declaration.statements) { + if (reportNode) + this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, (reportNode).range); + return; + } + + if (!this.checkTypeArguments(declaration.typeParameters, typeArguments, reportNode)) // reports if requested + return; + + let globalName: string = this.program.mangleInternalName(declaration); + + // inherit type arguments, i.e. from class + const typeArgumentsMap: Map = new Map(); + if (inheritedTypeArgumentsMap) + for (let [key, val] of (>inheritedTypeArgumentsMap)) + typeArgumentsMap.set(key, val); + + // set (possibly override) type arguments for this specific call + let i: i32, k: i32 = typeArguments.length; + if (k) { + for (i = 0; i < k; ++i) + typeArgumentsMap.set(declaration.typeParameters[i].identifier.name, typeArguments[i]); + globalName += typeArgumentsToString(typeArguments); + } + + if (this.functions.has(globalName)) { + if (reportNode) + this.error(DiagnosticCode.Duplicate_function_implementation, (reportNode).range); + return; + } + + // resolve parameters + k = declaration.parameters.length; + const parameterNames: string[] = new Array(k); + const parameterTypes: Type[] = new Array(k); + for (i = 0; i < k; ++i) { + parameterNames[i] = declaration.parameters[i].identifier.name; + const typeNode: TypeNode | null = declaration.parameters[i].type; + if (typeNode) { + const type: Type | null = this.resolveType(typeNode, typeArgumentsMap, true); // reports + if (type) + parameterTypes[i] = type; + else + return; + } else + return; // TODO: infer type? (currently reported by parser) + } + + // resolve return type + const typeNode: TypeNode | null = declaration.returnType; + let returnType: Type; + if (typeNode) { + const type: Type | null = this.resolveType(typeNode, typeArgumentsMap, true); // reports + if (type) + returnType = type; + else + return; + } else + return; // TODO: infer type? (currently reported by parser) + + // compile statements + const functionType: FunctionType = new FunctionType(typeArguments, parameterTypes, returnType, parameterNames); + this.functions.set(globalName, 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; + 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("", binaryenResultType, binaryenParamTypes); */ - throw new Error("not implemented"); + 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); } 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); + this.compileGlobal(declarations[i], isConst, isExport); } - compileGlobal(declaration: VariableDeclaration, isConst: bool): void { + compileGlobal(declaration: VariableDeclaration, isConst: bool, isExport: bool): void { const type: Type | null = declaration.type ? this.resolveType(declaration.type) : null; // reports if (!type) return; @@ -252,15 +362,55 @@ export class Compiler extends DiagnosticEmitter { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); return; } - if (!this.options.noEmit) { - const binaryenType: BinaryenType = typeToBinaryenType(type); - const initializer: BinaryenExpressionRef = declaration.initializer ? this.compileExpression(declaration.initializer, type) : typeToBinaryenZero(this.module, type); + 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 + if (initializeInStart) { + this.module.addGlobal(name, binaryenType, false, typeToBinaryenZero(this.module, type)); + this.startFunctionBody.push(initializer); + } else this.module.addGlobal(name, binaryenType, !isConst, initializer); - } - this.globals.add(name); + // 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); } compileNamespace(declaration: NamespaceDeclaration): void { + const members: Statement[] = declaration.members; + 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) + this.compileClass(member, []); + break; + + case NodeKind.ENUM: + this.compileEnum(member); + break; + + case NodeKind.FUNCTION: + if (!(member).typeParameters.length) + this.compileFunction(member, []); + break; + + case NodeKind.NAMESPACE: + this.compileNamespace(member); + break; + + case NodeKind.VARIABLE: + this.compileGlobals(member); + break; + + // TODO: some form of internal visibility? + // case NodeKind.EXPORT: + + default: + throw new Error("unexpected namespace member kind"); + } + } throw new Error("not implemented"); } @@ -296,11 +446,11 @@ export class Compiler extends DiagnosticEmitter { break; case NodeKind.VARIABLEDECLARATION: - this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, (declaration.parent).modifiers)); + this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, (declaration.parent).modifiers), member.range.source.isEntry); break; default: - throw new Error("unexpected declaration kind"); + throw new Error("unexpected export declaration kind"); } } else throw new Error("unexpected missing declaration"); @@ -322,20 +472,96 @@ export class Compiler extends DiagnosticEmitter { // types resolveType(node: TypeNode, typeArgumentsMap: Map | null = null, reportNotFound: bool = true): Type | null { - // TODO: resolve node and its arguments using typeArgumentsMap - // TODO: make types for classes in program.ts + + // resolve parameters + const k: i32 = node.parameters.length; + const paramTypes: Type[] = new Array(k); + for (let i: i32 = 0; i < k; ++i) { + const paramType: Type | null = this.resolveType(node.parameters[i], typeArgumentsMap, reportNotFound); + if (!paramType) + return null; + paramTypes[i] = paramType; + } + + let globalName: string = node.identifier.name; + if (k) // not a placeholder + globalName += typeArgumentsToString(paramTypes); + else if (typeArgumentsMap && (>typeArgumentsMap).has(globalName)) // possibly a placeholder + return (>typeArgumentsMap).get(globalName); + const types: Map = this.program.types; - const globalName: string = node.identifier.name; + + // resolve local type const localName: string = node.range.source.normalizedPath + "/" + globalName; if (types.has(localName)) return types.get(localName); + + // resolve global type if (types.has(globalName)) return types.get(globalName); + if (reportNotFound) this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName); return null; } + resolveExpressionType(expression: Expression, contextualType: Type): Type { + const previousType: Type = this.currentType; + const previousNoEmit: bool = this.module.noEmit; + this.module.noEmit = true; + this.compileExpression(expression, contextualType, false); // now performs a dry run + const type: Type = this.currentType; + this.currentType = previousType; + this.module.noEmit = previousNoEmit; + return type; + } + + resolveClassType(expression: Expression, typeArguments: TypeNode[], typeArgumentsMap: Map | null = null, create: bool = false): ClassType | null { + if (expression.kind == NodeKind.THIS) { + if (this.currentClass) + return this.currentClass; + this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); + return null; + } + if (expression.kind == NodeKind.IDENTIFIER) { + throw new Error("not implemented"); + } + if (expression.kind == NodeKind.ELEMENTACCESS) { + throw new Error("not implemented"); + } + if (expression.kind == NodeKind.PROPERTYACCESS) { + throw new Error("not implemented"); + } + return null; + } + + resolveFunctionType(expression: Expression, typeArguments: TypeNode[], typeArgumentsMap: Map | null = null, create: bool = false): FunctionType | null { + const k: i32 = typeArguments.length; + const resolvedTypeArguments: Type[] = new Array(k); + for (let i: i32 = 0; i < k; ++i) { + const type: Type | null = this.resolveType(typeArguments[i], typeArgumentsMap, true); // reports + if (!type) + return null; + resolvedTypeArguments[i] = type; + } + if (expression.kind == NodeKind.IDENTIFIER) { + let globalName: string = (expression).name; + if (k) + globalName += typeArgumentsToString(resolvedTypeArguments); + const localName = expression.range.source.normalizedPath + "/" + globalName; + if (this.functions.has(localName)) + return this.functions.get(localName); + 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) { + throw new Error("not implemented"); + } + return null; + } + // statements compileStatement(statement: Statement): BinaryenExpressionRef { @@ -395,15 +621,35 @@ export class Compiler extends DiagnosticEmitter { } compileBreakStatement(statement: BreakStatement): BinaryenExpressionRef { - throw new Error("not implemented"); + const context: string | null = this.currentFunction.breakContext; + if (context) + 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 { - throw new Error("not implemented"); + const context: string | null = this.currentFunction.breakContext; + if (context) + 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(); } compileDoStatement(statement: DoStatement): BinaryenExpressionRef { - throw new Error("not implemented"); + const label: string = this.currentFunction.enterBreakContext(); + const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32); + const body: BinaryenExpressionRef = this.compileStatement(statement.statement); + this.currentFunction.leaveBreakContext(); + const breakLabel: string = "break$" + label; + const continueLabel: string = "continue$" + label; + return this.module.createBlock(breakLabel, [ + this.module.createLoop(continueLabel, + this.module.createBlock(null, [ + body, + this.module.createBreak(continueLabel, condition) + ])) + ]); } compileEmptyStatement(statement: EmptyStatement): BinaryenExpressionRef { @@ -415,10 +661,24 @@ export class Compiler extends DiagnosticEmitter { } compileForStatement(statement: ForStatement): BinaryenExpressionRef { - const initializer: BinaryenExpressionRef = statement.initializer ? this.compileStatement(statement.initializer) : 0; - const condition: BinaryenExpressionRef = statement.condition ? this.compileExpression(statement.condition, Type.i32) : 0; - const incrementor: BinaryenExpressionRef = statement.incrementor ? this.compileExpression(statement.incrementor, Type.void) : 0; - throw new Error("not implemented"); + const context: string = this.currentFunction.enterBreakContext(); + const initializer: BinaryenExpressionRef = statement.initializer ? this.compileStatement(statement.initializer) : this.module.createNop(); + const condition: BinaryenExpressionRef = statement.condition ? this.compileExpression(statement.condition, Type.i32) : this.module.createI32(1); + const incrementor: BinaryenExpressionRef = statement.incrementor ? this.compileExpression(statement.incrementor, Type.void) : this.module.createNop(); + const body: BinaryenExpressionRef = this.compileStatement(statement.statement); + this.currentFunction.leaveBreakContext(); + const continueLabel: string = "continue$" + context; + const breakLabel: string = "break$" + context; + return this.module.createBlock(breakLabel, [ + initializer, + this.module.createLoop(continueLabel, this.module.createBlock(null, [ + this.module.createIf(condition, this.module.createBlock(null, [ + body, + incrementor, + this.module.createBreak(continueLabel) + ])) + ])) + ]); } compileIfStatement(statement: IfStatement): BinaryenExpressionRef { @@ -446,10 +706,35 @@ export class Compiler extends DiagnosticEmitter { compileTryStatement(statement: TryStatement): BinaryenExpressionRef { throw new Error("not implemented"); + // can't yet support something like: try { return ... } finally { ... } + // worthwhile to investigate lowering returns to block results (here)? } compileVariableStatement(statement: VariableStatement): BinaryenExpressionRef { - throw new Error("not implemented"); + // top-level variables become globals + if (this.currentFunction == this.startFunction) { + this.compileGlobals(statement); + return this.module.createNop(); + } + // other variables become locals + const declarations: VariableDeclaration[] = statement.declarations; + const initializers: BinaryenExpressionRef[] = new Array(); + for (let i: i32 = 0, k = declarations.length; i < k; ++i) { + const declaration: VariableDeclaration = declarations[i]; + if (declaration.type) { + const name: string = declaration.identifier.name; + const type: Type | null = this.resolveType(declaration.type, this.currentFunction.typeArgumentsMap, true); // reports + if (type) { + if (this.currentFunction.locals.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); // recoverable + else + this.currentFunction.addLocal(type, name); + if (declaration.initializer) + initializers.push(this.compileAssignment(declaration.identifier, declaration.initializer, Type.void)); + } + } + } + return initializers.length ? this.module.createBlock(null, initializers) : this.module.createNop(); } compileWhileStatement(statement: WhileStatement): BinaryenExpressionRef { @@ -541,7 +826,7 @@ export class Compiler extends DiagnosticEmitter { // void to any if (fromType.kind == TypeKind.VOID) - throw new Error("illegal conversion"); + throw new Error("unexpected conversion from void"); // any to void if (toType.kind == TypeKind.VOID) @@ -773,6 +1058,9 @@ export class Compiler extends DiagnosticEmitter { this.currentType = Type.bool; break; + case Token.EQUALS: + return this.compileAssignment(expression.left, expression.right, contextualType); + case Token.PLUS_EQUALS: compound = Token.EQUALS; case Token.PLUS: @@ -835,7 +1123,7 @@ export class Compiler extends DiagnosticEmitter { left = this.compileExpression(expression.left, contextualType, false); right = this.compileExpression(expression.right, this.currentType); if (this.currentType.isAnyFloat) - throw new Error("not implemented"); // TODO: fmod + throw new Error("not implemented"); // TODO: internal fmod op = this.currentType.isLongInteger ? BinaryOp.RemI64 : BinaryOp.RemI32; @@ -908,17 +1196,56 @@ export class Compiler extends DiagnosticEmitter { default: throw new Error("not implemented"); } - - return compound - ? this.compileAssignment(expression.left, this.module.createBinary(op, left, right), this.currentType != Type.void) - : this.module.createBinary(op, left, right); + if (compound) { + right = this.module.createBinary(op, left, right); + return this.compileAssignmentWithValue(expression.left, right, contextualType != Type.void); + } + return this.module.createBinary(op, left, right); } - compileAssignment(expression: Expression, value: BinaryenExpressionRef, tee: bool = false): BinaryenExpressionRef { + compileAssignment(expression: Expression, valueExpression: Expression, contextualType: Type): BinaryenExpressionRef { + this.currentType = this.resolveExpressionType(expression, contextualType); + return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, this.currentType), contextualType != Type.void); + } + + compileAssignmentWithValue(expression: Expression, valueWithCorrectType: BinaryenExpressionRef, tee: bool = false): BinaryenExpressionRef { + if (expression.kind == NodeKind.IDENTIFIER) { + const name: string = (expression).name; + const locals: Map = this.currentFunction.locals; + if (locals.has(name)) { + const local: LocalType = locals.get(name); + if (tee) { + this.currentType = local.type; + return this.module.createTeeLocal(local.index, valueWithCorrectType); + } + this.currentType = Type.void; + return this.module.createSetLocal(local.index, valueWithCorrectType); + } + const globals: Map = this.globals; + if (globals.has(name)) { + const globalType: Type = globals.get(name); + if (tee) { + const globalBinaryenType: BinaryenType = typeToBinaryenType(globalType); + this.currentType = globalType; + return this.module.createBlock(null, [ // teeGlobal + this.module.createSetGlobal(name, valueWithCorrectType), + this.module.createGetGlobal(name, globalBinaryenType) + ], globalBinaryenType); + } + this.currentType = Type.void; + return this.module.createSetGlobal(name, valueWithCorrectType); + } + } throw new Error("not implemented"); } compileCallExpression(expression: CallExpression, contextualType: Type): BinaryenExpressionRef { + const functionType: FunctionType | null = this.resolveFunctionType(expression.expression, expression.typeArguments, this.currentFunction.typeArgumentsMap, true); + if (!functionType) + return this.module.createUnreachable(); + // TODO: compile if not yet compiled + // TODO: validate parameters and call + // this.module.createCall(functionType.ref, operands, typeToBinaryenType(functionType.returnType)); throw new Error("not implemented"); } @@ -927,7 +1254,20 @@ export class Compiler extends DiagnosticEmitter { } compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): BinaryenExpressionRef { - throw new Error("not implemented"); + const name: string = expression.name; + const locals: Map = this.currentFunction.locals; + if (locals.has(name)) { + const local: LocalType = locals.get(name); + 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)); + } + this.error(DiagnosticCode.Cannot_find_name_0, expression.range, name); + return this.module.createUnreachable(); } compileLiteralExpression(expression: LiteralExpression, contextualType: Type): BinaryenExpressionRef { @@ -1006,9 +1346,8 @@ export class Compiler extends DiagnosticEmitter { binaryenType = BinaryenType.I32; binaryenOne = this.module.createI32(1); } - const getValue: BinaryenExpressionRef = this.compileExpression(expression.expression, contextualType); - const setValue: BinaryenExpressionRef = this.compileAssignment(expression.expression, this.module.createBinary(op, getValue, binaryenOne), false); // reports + const setValue: BinaryenExpressionRef = this.compileAssignmentWithValue(expression.expression, this.module.createBinary(op, getValue, binaryenOne), false); // reports return this.module.createBlock(null, [ getValue, @@ -1017,16 +1356,18 @@ export class Compiler extends DiagnosticEmitter { } compileUnaryPrefixExpression(expression: UnaryPrefixExpression, contextualType: Type): BinaryenExpressionRef { + const operandExpression: Expression = expression.expression; + let operand: BinaryenExpressionRef; let op: UnaryOp; switch (expression.operator) { case Token.PLUS: - return this.compileExpression(expression.expression, contextualType); // noop + return this.compileExpression(operandExpression, contextualType); case Token.MINUS: - operand = this.compileExpression(expression.expression, contextualType); + operand = this.compileExpression(operandExpression, contextualType); if (this.currentType == Type.f32) op = UnaryOp.NegF32; else if (this.currentType == Type.f64) @@ -1038,27 +1379,27 @@ export class Compiler extends DiagnosticEmitter { break; case Token.PLUS_PLUS: - operand = this.compileExpression(expression.expression, contextualType); + operand = this.compileExpression(operandExpression, contextualType); return this.currentType == Type.f32 - ? this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.AddF32, operand, this.module.createF32(1)), contextualType != Type.void) + ? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddF32, operand, this.module.createF32(1)), contextualType != Type.void) : this.currentType == Type.f64 - ? this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.AddF64, operand, this.module.createF64(1)), contextualType != Type.void) + ? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddF64, operand, this.module.createF64(1)), contextualType != Type.void) : this.currentType.isLongInteger - ? this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.AddI64, operand, this.module.createI64(1, 0)), contextualType != Type.void) - : this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.AddI32, operand, this.module.createI32(1)), contextualType != Type.void); + ? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddI64, operand, this.module.createI64(1, 0)), contextualType != Type.void) + : this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.AddI32, operand, this.module.createI32(1)), contextualType != Type.void); case Token.MINUS_MINUS: - operand = this.compileExpression(expression.expression, contextualType); + operand = this.compileExpression(operandExpression, contextualType); return this.currentType == Type.f32 - ? this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.SubF32, operand, this.module.createF32(1)), contextualType != Type.void) + ? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubF32, operand, this.module.createF32(1)), contextualType != Type.void) : this.currentType == Type.f64 - ? this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.SubF64, operand, this.module.createF64(1)), contextualType != Type.void) + ? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubF64, operand, this.module.createF64(1)), contextualType != Type.void) : this.currentType.isLongInteger - ? this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.SubI64, operand, this.module.createI64(1, 0)), contextualType != Type.void) - : this.compileAssignment(expression.expression, this.module.createBinary(BinaryOp.SubI32, operand, this.module.createI32(1)), contextualType != Type.void); + ? this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubI64, operand, this.module.createI64(1, 0)), contextualType != Type.void) + : this.compileAssignmentWithValue(operandExpression, this.module.createBinary(BinaryOp.SubI32, operand, this.module.createI32(1)), contextualType != Type.void); case Token.EXCLAMATION: - operand = this.compileExpression(expression.expression, Type.bool, false); + operand = this.compileExpression(operandExpression, Type.bool, false); if (this.currentType == Type.f32) { this.currentType = Type.bool; return this.module.createBinary(BinaryOp.EqF32, operand, this.module.createF32(0)); @@ -1074,7 +1415,7 @@ export class Compiler extends DiagnosticEmitter { break; case Token.TILDE: - operand = this.compileExpression(expression.expression, contextualType.isAnyFloat ? Type.i64 : contextualType); + operand = this.compileExpression(operandExpression, contextualType.isAnyFloat ? Type.i64 : contextualType); return this.currentType.isLongInteger ? this.module.createBinary(BinaryOp.XorI64, operand, this.module.createI64(-1, -1)) : this.module.createBinary(BinaryOp.XorI32, operand, this.module.createI32(-1)); @@ -1082,7 +1423,6 @@ export class Compiler extends DiagnosticEmitter { default: throw new Error("not implemented"); } - return this.module.createUnary(op, operand); } } @@ -1101,6 +1441,14 @@ function typeToBinaryenType(type: Type): BinaryenType { : BinaryenType.None; } +function typesToBinaryenTypes(types: Type[]): BinaryenType[] { + const k: i32 = types.length; + const ret: BinaryenType[] = new Array(k); + for (let i: i32 = 0; i < k; ++i) + ret[i] = typeToBinaryenType(types[i]); + return ret; +} + function typeToBinaryenZero(module: Module, type: Type): BinaryenExpressionRef { return type.kind == TypeKind.F32 ? module.createF32(0) @@ -1135,7 +1483,7 @@ function typeToSignatureNamePart(type: Type): string { function typesToSignatureName(paramTypes: Type[], returnType: Type): string { sb.length = 0; - for (let i: i32 = 0, k = paramTypes.length; i < k; ++i) + for (let i: i32 = 0, k: i32 = paramTypes.length; i < k; ++i) sb.push(typeToSignatureNamePart(paramTypes[i])); sb.push(typeToSignatureNamePart(returnType)); return sb.join(""); diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 9b4f6257..7ca66c7e 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -19,6 +19,8 @@ export enum DiagnosticCode { An_accessor_cannot_have_type_parameters = 1094, A_set_accessor_cannot_have_a_return_type_annotation = 1095, Type_parameter_list_cannot_be_empty = 1098, + A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement = 1104, + A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement = 1105, A_return_statement_can_only_be_used_within_a_function_body = 1108, Expression_expected = 1109, Type_expected = 1110, @@ -46,8 +48,11 @@ export enum DiagnosticCode { Generic_type_0_requires_1_type_argument_s = 2314, Type_0_is_not_generic = 2315, Type_0_is_not_assignable_to_type_1 = 2322, + _this_cannot_be_referenced_in_current_location = 2332, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, - Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391 + Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391, + Duplicate_function_implementation = 2393, + Expected_0_type_arguments_but_got_1 = 2558 } export function diagnosticCodeToString(code: DiagnosticCode): string { @@ -70,6 +75,8 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1094: return "An accessor cannot have type parameters."; case 1095: return "A 'set' accessor cannot have a return type annotation."; case 1098: return "Type parameter list cannot be empty."; + case 1104: return "A 'continue' statement can only be used within an enclosing iteration statement."; + case 1105: return "A 'break' statement can only be used within an enclosing iteration or switch statement."; case 1108: return "A 'return' statement can only be used within a function body."; case 1109: return "Expression expected."; case 1110: return "Type expected."; @@ -97,8 +104,11 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2314: return "Generic type '{0}' requires {1} type argument(s)."; case 2315: return "Type '{0}' is not generic."; case 2322: return "Type '{0}' is not assignable to type '{1}'."; + case 2332: return "'this' cannot be referenced in current location."; case 2357: return "The operand of an increment or decrement operator must be a variable or a property access."; 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}."; default: return ""; } } diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index ccf2b8ed..249fd24b 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -18,6 +18,8 @@ "An accessor cannot have type parameters.": 1094, "A 'set' accessor cannot have a return type annotation.": 1095, "Type parameter list cannot be empty.": 1098, + "A 'continue' statement can only be used within an enclosing iteration statement.": 1104, + "A 'break' statement can only be used within an enclosing iteration or switch statement.": 1105, "A 'return' statement can only be used within a function body.": 1108, "Expression expected.": 1109, "Type expected.": 1110, @@ -46,6 +48,9 @@ "Generic type '{0}' requires {1} type argument(s).": 2314, "Type '{0}' is not generic.": 2315, "Type '{0}' is not assignable to type '{1}'.": 2322, + "'this' cannot be referenced in current location.": 2332, "The operand of an increment or decrement operator must be a variable or a property access.": 2357, - "Function implementation is missing or not immediately following the declaration.": 2391 + "Function implementation is missing or not immediately following the declaration.": 2391, + "Duplicate function implementation.": 2393, + "Expected {0} type arguments, but got {1}.": 2558 } diff --git a/src/parser.ts b/src/parser.ts index 08aa062d..7984bfe3 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -10,7 +10,7 @@ import { Program } from "./program"; import { Tokenizer, Token, Range } from "./tokenizer"; import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics"; -import { normalizePath } from "./util"; +import { normalizePath, I64 } from "./util"; import { NodeKind, @@ -233,9 +233,9 @@ export class Parser extends DiagnosticEmitter { // this } else if (token == Token.THIS) { - type = TypeNode.create(Expression.createIdentifier("this", tn.range()), [], false, tn.range(startPos, tn.pos)); + type = TypeNode.create(Expression.createThis(tn.range()), [], false, tn.range(startPos, tn.pos)); - // true, false + // true } else if (token == Token.TRUE || token == Token.FALSE) { type = TypeNode.create(Expression.createIdentifier("bool", tn.range()), [], false, tn.range(startPos, tn.pos)); @@ -951,6 +951,7 @@ export class Parser extends DiagnosticEmitter { } parseExpressionStatement(tn: Tokenizer): ExpressionStatement | null { + // at previous token const expr: Expression | null = this.parseExpression(tn); if (!expr) return null; @@ -961,7 +962,7 @@ export class Parser extends DiagnosticEmitter { parseForStatement(tn: Tokenizer): ForStatement | null { // at 'for': '(' Statement? Expression? ';' Expression? ')' Statement - const startRange: Range = tn.range(); + const startPos: i32 = tn.tokenPos; if (tn.skip(Token.OPENPAREN)) { const initializer: Statement | null = this.parseStatement(tn); // skips the semicolon (actually an expression) if (!initializer) @@ -976,10 +977,13 @@ export class Parser extends DiagnosticEmitter { const incrementor: Expression | null = this.parseExpression(tn); if (!incrementor) return null; - const statement: Statement | null = this.parseStatement(tn); - if (!statement) - return null; - return Statement.createFor(initializer, condition, incrementor, statement, Range.join(startRange, tn.range())); + if (tn.skip(Token.CLOSEPAREN)) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + return Statement.createFor(initializer, condition, incrementor, statement, tn.range(startPos, tn.pos)); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ")"); } else this.error(DiagnosticCode._0_expected, tn.range(), ";"); } else @@ -1095,8 +1099,7 @@ export class Parser extends DiagnosticEmitter { } parseTryStatement(tn: Tokenizer): TryStatement | null { - // at 'try': '{' Statement* '}' 'catch' '(' VariableMember ')' '{' Statement* '}' ';'? - // TODO: 'finally' '{' Statement* '}' + // at 'try': '{' Statement* '}' ('catch' '(' VariableMember ')' '{' Statement* '}')? ('finally' '{' Statement* '}'? ';'? const startRange: Range = tn.range(); if (tn.skip(Token.OPENBRACE)) { const statements: Statement[] = new Array(); @@ -1106,31 +1109,55 @@ export class Parser extends DiagnosticEmitter { return null; statements.push(stmt); } + let catchVariable: IdentifierExpression | null = null; + let catchStatements: Statement[] | null = null; + let finallyStatements: Statement[] | null = null; if (tn.skip(Token.CATCH)) { - if (tn.skip(Token.OPENPAREN)) { - const catchVariable: VariableDeclaration | null = this.parseVariableDeclaration(tn); - if (!catchVariable) - return null; - if (tn.skip(Token.CLOSEPAREN)) { - if (tn.skip(Token.OPENBRACE)) { - const catchStatements: Statement[] = new Array(); - while (!tn.skip(Token.CLOSEBRACE)) { - const stmt: Statement | null = this.parseStatement(tn); - if (!stmt) - return null; - catchStatements.push(stmt); - } - const ret: TryStatement = Statement.createTry(statements, catchVariable, catchStatements, Range.join(startRange, tn.range())); - tn.skip(Token.SEMICOLON); - return ret; - } else - this.error(DiagnosticCode._0_expected, tn.range(), "{"); - } else - this.error(DiagnosticCode._0_expected, tn.range(), ")"); - } else + if (!tn.skip(Token.OPENPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(), "("); - } else + return null; + } + if (!tn.skip(Token.IDENTIFIER)) { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + catchVariable = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + if (!tn.skip(Token.CLOSEPAREN)) { + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + return null; + } + if (!tn.skip(Token.OPENBRACE)) { + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + catchStatements = new Array(); + while (!tn.skip(Token.CLOSEBRACE)) { + const stmt: Statement | null = this.parseStatement(tn); + if (!stmt) + return null; + catchStatements.push(stmt); + } + } + if (tn.skip(Token.FINALLY)) { + if (!tn.skip(Token.OPENBRACE)) { + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + finallyStatements = new Array(); + while (!tn.skip(Token.CLOSEBRACE)) { + const stmt: Statement | null = this.parseStatement(tn); + if (!stmt) + return null; + finallyStatements.push(stmt); + } + } + if (!(catchStatements || finallyStatements)) { this.error(DiagnosticCode._0_expected, tn.range(), "catch"); + return null; + } + const ret: TryStatement = Statement.createTry(statements, catchVariable, catchStatements, finallyStatements, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); return null; @@ -1164,12 +1191,12 @@ export class Parser extends DiagnosticEmitter { const token: Token = tn.next(); const startPos: i32 = tn.tokenPos; - if (token == Token.FALSE) - return Expression.createIdentifier("false", tn.range()); if (token == Token.NULL) - return Expression.createIdentifier("null", tn.range()); + return Expression.createNull(tn.range()); if (token == Token.TRUE) - return Expression.createIdentifier("true", tn.range()); + return Expression.createTrue(tn.range()); + if (token == Token.FALSE) + return Expression.createFalse(tn.range()); let p: Precedence = determinePrecedencePrefix(token); if (p != Precedence.INVALID) { @@ -1262,6 +1289,10 @@ export class Parser extends DiagnosticEmitter { return Expression.createAssertion(AssertionKind.PREFIX, expr, toType, tn.range(startPos, tn.pos)); } + // IdentifierExpression + case Token.IDENTIFIER: + return Expression.createIdentifier(tn.readIdentifier(), tn.range(startPos, tn.pos)); + // StringLiteralExpression case Token.STRINGLITERAL: return Expression.createStringLiteral(tn.readString(), tn.range(startPos, tn.pos)); @@ -1278,10 +1309,6 @@ export class Parser extends DiagnosticEmitter { case Token.REGEXPLITERAL: return Expression.createRegexpLiteral(tn.readRegexp(), tn.range(startPos, tn.pos)); - // IdentifierExpression - case Token.IDENTIFIER: - return Expression.createIdentifier(tn.readIdentifier(), tn.range(startPos, tn.pos)); - default: this.error(DiagnosticCode.Expression_expected, tn.range()); return null; diff --git a/src/program.ts b/src/program.ts index e5e5ae58..07a59a76 100644 --- a/src/program.ts +++ b/src/program.ts @@ -39,6 +39,8 @@ class QueuedImport { declaration: ImportDeclaration; } +const typesStub: Map = new Map(); + export class Program extends DiagnosticEmitter { sources: Source[]; @@ -48,7 +50,7 @@ export class Program extends DiagnosticEmitter { /** Internal map of names to declarations. */ names: Map = new Map(); /** Separate map of internal type names to declarations. */ - types: Map = new Map(); + types: Map = typesStub; /** Separate map of internal export names to declarations. */ exports: Map = new Map(); @@ -59,8 +61,22 @@ export class Program extends DiagnosticEmitter { initialize(target: Target): void { this.target = target; - - initializeBasicTypes(this.types, target); + this.types = new Map([ + ["i8", Type.i8], + ["i16", Type.i16], + ["i32", Type.i32], + ["i64", Type.i64], + ["isize", target == Target.WASM64 ? Type.isize64 : Type.isize32], + ["u8", Type.u8], + ["u16", Type.u16], + ["u32", Type.u32], + ["u64", Type.u64], + ["usize", target == Target.WASM64 ? Type.usize64 : Type.usize32], + ["bool", Type.bool], + ["f32", Type.f32], + ["f64", Type.f64], + ["void", Type.void] + ]); const queuedExports: Map = new Map(); const queuedImports: QueuedImport[] = new Array(); @@ -127,8 +143,8 @@ export class Program extends DiagnosticEmitter { for (let i: i32 = 0, k: i32 = queuedImports.length; i < k; ++i) { const queuedImport: QueuedImport = queuedImports[i]; const internalName: string = queuedImport.internalName; - let importName: string = queuedImport.importName; const seen: Set = new Set(); + let importName: string = queuedImport.importName; while (queuedExports.has(importName)) { const queuedExport: QueuedExport = queuedExports.get(importName); importName = queuedExport.importName; @@ -327,7 +343,7 @@ export class Program extends DiagnosticEmitter { private initializeVariables(statement: VariableStatement, isNamespaceMember: bool = false): void { const declarations: VariableDeclaration[] = statement.declarations; const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers); - for (let i: i32 = 0, k = declarations.length; i < k; ++i) { + for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; const internalName: string = this.mangleInternalName(declaration); this.addName(internalName, declaration); @@ -394,20 +410,3 @@ export class Program extends DiagnosticEmitter { throw new Error("unexpected parent"); } } - -function initializeBasicTypes(types: Map, target: Target): void { - types.set("i8", Type.i8); - types.set("i16", Type.i16); - types.set("i32", Type.i32); - types.set("i64", Type.i64); - types.set("isize", target == Target.WASM64 ? Type.isize64 : Type.isize32); - types.set("u8", Type.u8); - types.set("u16", Type.u16); - types.set("u32", Type.u32); - types.set("u64", Type.u64); - types.set("usize", target == Target.WASM64 ? Type.usize64 : Type.usize32); - types.set("bool", Type.bool); - types.set("f32", Type.f32); - types.set("f64", Type.f64); - types.set("void", Type.void); -} diff --git a/src/tokenizer.ts b/src/tokenizer.ts index aade5d26..bec2c0f5 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -475,6 +475,9 @@ export class Tokenizer extends DiagnosticEmitter { case CharCode.SLASH: if (++this.pos < this.end) { if (text.charCodeAt(this.pos) == CharCode.SLASH) { // single-line comment + if (this.pos + 1 < this.end && text.charCodeAt(this.pos + 1) == CharCode.SLASH) { + // TODO: triple-slash directives, i.e. '/// ' + } while (++this.pos < this.end) { if (isLineBreak(text.charCodeAt(this.pos))) break; @@ -690,7 +693,7 @@ export class Tokenizer extends DiagnosticEmitter { const posBefore: i32 = this.pos; const tokenBefore: Token = this.token; const tokenPosBefore: i32 = this.tokenPos; - if ((this.nextToken = this.unsafeNext(token == Token.IDENTIFIER)) == token) { + if ((this.token = this.unsafeNext(token == Token.IDENTIFIER)) == token) { this.nextToken = -1; return true; } else { diff --git a/src/tsconfig.json b/src/tsconfig.json index e1ac027c..b004e967 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -27,7 +27,6 @@ "index.ts", "parser.ts", "program.ts", - "reflection.ts", "tokenizer.ts", "types.ts", "util.ts", diff --git a/src/types.ts b/src/types.ts index e10e5a34..67d7f8de 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { sb } from "./util"; + export const enum TypeKind { // signed integers @@ -54,12 +56,14 @@ export class Type { } asNullable(): Type { + if (this.kind != TypeKind.USIZE) + throw new Error("unexpected non-usize nullable"); if (!this.nullableType) (this.nullableType = new Type(this.kind, this.size)).nullable = true; return this.nullableType; } - toString(): string { + toString(kindOnly: bool = false): string { switch (this.kind) { case TypeKind.I8: return "i8"; case TypeKind.I16: return "i16"; @@ -69,7 +73,7 @@ export class Type { case TypeKind.U16: return "u16"; case TypeKind.U32: return "u32"; case TypeKind.U64: return "u64"; - case TypeKind.USIZE: return "usize"; + case TypeKind.USIZE: return this.classType && !kindOnly ? this.classType.toString() : "usize"; case TypeKind.BOOL: return "bool"; case TypeKind.F32: return "f32"; case TypeKind.F64: return "f64"; @@ -99,40 +103,99 @@ export class Type { export class FunctionType { - returnType: Type; + typeArguments: Type[]; parameterTypes: Type[]; + returnType: Type; + additionalLocals: Type[]; typeArgumentsMap: Map | null = null; + locals: Map = new Map(); + breakContext: string | null = null; private breakMajor: i32 = 0; private breakMinor: i32 = 0; - constructor(returnType: Type, parameterTypes: Type[]) { - this.returnType = returnType; + constructor(typeArguments: Type[], parameterTypes: Type[], returnType: Type, parameterNames: string[] | null = null) { + this.typeArguments = typeArguments; this.parameterTypes = parameterTypes; + this.returnType = returnType; this.additionalLocals = new Array(); + if (parameterNames) { + if (parameterTypes.length != (parameterNames).length) + throw new Error("unexpected parameter count mismatch");; + for (let i: i32 = 0, k: i32 = parameterTypes.length; i < k; ++i) { + const name: string = (parameterNames)[i]; + if (this.locals.has(name)) + throw new Error("duplicate parameter name"); + this.locals.set(name, new LocalType(i, parameterTypes[i])); + } + } } enterBreakContext(): string { if (!this.breakMinor) this.breakMajor++; - return this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10); + return this.breakContext = this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10); } leaveBreakContext(): void { if (--this.breakMinor < 0) throw new Error("unexpected unbalanced break context"); + if (this.breakMinor == 0 && !--this.breakMajor) + this.breakContext = null; + } + + addLocal(type: Type, name: string | null = null): i32 { + // internal locals don't necessarily need names if referenced by index only + if (name && this.locals.has(name)) + throw new Error("duplicate local name"); + const index: i32 = this.parameterTypes.length + this.additionalLocals.length; + this.additionalLocals.push(type); + if (name) + this.locals.set(name, new LocalType(index, type)); + return index; + } +} + +export class LocalType { + + index: i32; + type: Type; + + constructor(index: i32, type: Type) { + this.index = index; + this.type = type; } } export class ClassType { - type: Type; - typeArgumentsMap: Map | null = null; + name: string; + typeArguments: Type[]; base: ClassType | null; - constructor(usizeType: Type, base: ClassType | null = null) { - this.type = usizeType.asClass(this); + type: Type; + typeArgumentsMap: Map | null = null; + + constructor(name: string, usizeType: Type, typeArguments: Type[], base: ClassType | null = null) { + this.name = name; + this.typeArguments = typeArguments; this.base = base; + this.type = usizeType.asClass(this); + } + + toString(): string { + let str: string = this.typeArguments.length ? this.name + typeArgumentsToString(this.typeArguments) : this.name; + return this.type.nullable ? str + " | null" : str; } } + +export function typeArgumentsToString(typeArguments: Type[]): string { + const k: i32 = typeArguments.length; + if (!k) + return ""; + sb.length = 0; + for (let i: i32 = 0; i < k; ++i) + sb[i] = typeArguments[i].toString(); + return "<" + sb.join(",") + ">"; +} diff --git a/src/util.ts b/src/util.ts index cf4b3354..e2be4b97 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,7 @@ -export { I64, U64 } from "./util/i64"; export { CharCode, isLineBreak} from "./util/charcode"; + +export { I64, U64 } from "./util/i64"; + export { normalize as normalizePath, resolve as resolvePath, dirname } from "./util/path"; + export const sb: string[] = new Array(256); // shared string builder. 64-bit without growing: (4+4+8) + 8*256 = 16b + 2kb diff --git a/tests/compiler.ts b/tests/compiler.ts index cca6e54b..3d8c94ce 100644 --- a/tests/compiler.ts +++ b/tests/compiler.ts @@ -2,12 +2,18 @@ import "../src/glue/js"; import { Compiler } from "../src/compiler"; import { Parser } from "../src/parser"; -const files: Map = new Map([ +/* const files: Map = new Map([ ["main", `import { Test as TestAlias } from "./a"; export { TestAlias } from "./d"; if (1) {} export const a: i32 = 123;`], ["a", `export { Test } from "./b";`], ["b", `export { Test } from "./c";`], - ["c", `export enum Test { ONE = 1, TWO = 2 }`], + ["c", `export enum Test { ONE = 1, TWO = 1 + 1 }`], ["d", `export { Test as TestAlias } from "./b";`] +]); */ + +const files: Map = new Map([ + ["main", ` + export function add(a: i32, b: i32): i32 { let c: i32 = a + b; return c; } + `] ]); const parser = new Parser(); @@ -27,9 +33,10 @@ const module = compiler.compile(); console.log("names", program.names.keys()); console.log("exports", program.exports.keys()); -// module.optimize(); +module.optimize(); // module.validate(); // global initializers can't use i32.add etc. yet -_BinaryenModulePrint(module.ref); +if (!module.noEmit) + _BinaryenModulePrint(module.ref); /* console.log("--- statements ---"); compiler.statements.forEach(stmt => {