From f55fc7022010b4a49cd8bfcdb835c189877c4fb1 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Mon, 2 Oct 2017 12:52:15 +0200 Subject: [PATCH] Progress; Restructuring --- .gitignore | 2 + src/ast.ts | 186 ++++++++++---- src/binaryen.d.ts | 2 +- src/binaryen.ts | 2 +- src/compiler.ts | 375 ++++++++++++++++++++-------- src/diagnosticMessages.generated.ts | 4 + src/diagnosticMessages.json | 2 + src/diagnostics.ts | 2 +- src/index.ts | 2 +- src/parser.ts | 126 +++++++--- src/program.ts | 256 +++++++++---------- src/reflection.ts | 310 ----------------------- src/tokenizer.ts | 2 +- src/tsconfig.json | 6 +- src/types.ts | 138 ++++++++++ src/util.ts | 235 +---------------- src/util/charcode.ts | 140 +++++++++++ src/util/path.ts | 90 +++++++ tests/compiler.ts | 12 +- tests/parser/fixtures/function.ts | 3 + 20 files changed, 1008 insertions(+), 887 deletions(-) delete mode 100644 src/reflection.ts create mode 100644 src/types.ts create mode 100644 src/util/charcode.ts create mode 100644 src/util/path.ts diff --git a/.gitignore b/.gitignore index 6426b958..1ee5e907 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ npm-debug.* out/ +raw/ + diff --git a/src/ast.ts b/src/ast.ts index 13f668ef..b76b9ac6 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -53,6 +53,7 @@ │ ├ VariableStatement <> VariableDeclaration │ └ WhileStatement ├ Parameter + ├ Source ├ SwitchCase ├ TypeNode └ TypeParameter @@ -65,8 +66,8 @@ */ -import { Token, operatorTokenToString, Range } from "./tokenizer"; -import { CharCode, I64, sb } from "./util"; +import { Token, Tokenizer, operatorTokenToString, Range } from "./tokenizer"; +import { CharCode, I64, normalizePath, resolvePath } from "./util"; export { Range } from "./tokenizer"; @@ -106,6 +107,7 @@ export enum NodeKind { BREAK, CLASS, // is also declaration CONTINUE, + DECORATOR, DO, EMPTY, ENUM, // is also declaration @@ -202,7 +204,7 @@ export class TypeNode extends Node { this.identifier.serialize(sb); if (this.parameters.length) { sb.push("<"); - for (let i: i32 = 0; i < this.parameters.length; ++i) { + for (let i: i32 = 0, k: i32 = this.parameters.length; i < k; ++i) { if (i > 0) sb.push(", "); this.parameters[i].serialize(sb); @@ -398,7 +400,7 @@ export class ArrayLiteralExpression extends LiteralExpression { serialize(sb: string[]): void { sb.push("["); - for (let i: i32 = 0; i < this.elementExpressions.length; ++i) { + for (let i: i32 = 0, k: i32 = this.elementExpressions.length; i < k; ++i) { if (i > 0) sb.push(", "); if (this.elementExpressions[i]) @@ -410,7 +412,7 @@ export class ArrayLiteralExpression extends LiteralExpression { serializeAsTree(sb: string[], indent: i32 = 0): void { pushIndent(sb, indent++); sb.push("[\n"); - for (let i: i32 = 0; i < this.elementExpressions.length; ++i) { + for (let i: i32 = 0, k: i32 = this.elementExpressions.length; i < k; ++i) { pushIndent(sb, indent); if (i > 0) sb.push(",\n"); @@ -499,9 +501,10 @@ export class CallExpression extends Expression { serialize(sb: string[]): void { this.expression.serialize(sb); + let i: i32, k: i32; if (this.typeArguments.length) { sb.push("<"); - for (let i: i32 = 0; i < this.typeArguments.length; ++i) { + for (i = 0, k = this.typeArguments.length; i < k; ++i) { if (i > 0) sb.push(", "); this.typeArguments[i].serialize(sb); @@ -509,7 +512,7 @@ export class CallExpression extends Expression { sb.push(">("); } else sb.push("("); - for (let i: i32 = 0; i < this.arguments.length; ++i) { + for (i = 0, k = this.arguments.length; i < k; ++i) { if (i > 0) sb.push(", "); this.arguments[i].serialize(sb); @@ -806,7 +809,7 @@ export abstract class Statement extends Node { return stmt; } - static createClass(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], extendsType: TypeNode | null, implementsTypes: TypeNode[], members: DeclarationStatement[], range: Range): ClassDeclaration { + static createClass(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], extendsType: TypeNode | null, implementsTypes: TypeNode[], members: DeclarationStatement[], decorators: DecoratorStatement[], range: Range): ClassDeclaration { const stmt: ClassDeclaration = new ClassDeclaration(); stmt.range = range; let i: i32, k: i32; @@ -816,6 +819,7 @@ export abstract class Statement extends Node { if (stmt.extendsType = extendsType) (extendsType).parent = stmt; for (i = 0, k = (stmt.implementsTypes = implementsTypes).length; i < k; ++i) implementsTypes[i].parent = stmt; for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; + for (i = 0, k = (stmt.decorators = decorators).length; i < k; ++i) decorators[i].parent = stmt; return stmt; } @@ -826,6 +830,14 @@ export abstract class Statement extends Node { return stmt; } + static createDecorator(expression: Expression, args: Expression[], range: Range): DecoratorStatement { + const stmt: DecoratorStatement = new DecoratorStatement(); + stmt.range = range; + (stmt.expression = expression).parent = stmt; + for (let i: i32 = 0, k: i32 = (stmt.arguments = args).length; i < k; ++i) args[i].parent = stmt; + return stmt; + } + static createDo(statement: Statement, condition: Expression, range: Range): DoStatement { const stmt: DoStatement = new DoStatement(); stmt.range = range; @@ -865,6 +877,7 @@ export abstract class Statement extends Node { 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); return stmt; } @@ -905,6 +918,7 @@ export abstract class Statement extends Node { 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); return stmt; } @@ -926,7 +940,7 @@ export abstract class Statement extends Node { return stmt; } - static createField(modifiers: Modifier[], identifier: IdentifierExpression, type: TypeNode | null, initializer: Expression | null, range: Range): FieldDeclaration { + static createField(modifiers: Modifier[], identifier: IdentifierExpression, type: TypeNode | null, initializer: Expression | null, decorators: DecoratorStatement[], range: Range): FieldDeclaration { const stmt: FieldDeclaration = new FieldDeclaration(); stmt.range = range; let i: i32, k: i32; @@ -934,6 +948,7 @@ export abstract class Statement extends Node { (stmt.identifier = identifier).parent = stmt; if (stmt.type = type) (type).parent = stmt; if (stmt.initializer = initializer) (initializer).parent = stmt; + for (i = 0, k = (stmt.decorators = decorators).length; i < k; ++i) decorators[i].parent = stmt; return stmt; } @@ -965,7 +980,7 @@ export abstract class Statement extends Node { return elem; } - static createFunction(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], parameters: Parameter[], returnType: TypeNode | null, statements: Statement[] | null, range: Range): FunctionDeclaration { + static createFunction(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], parameters: Parameter[], returnType: TypeNode | null, statements: Statement[] | null, decorators: DecoratorStatement[], range: Range): FunctionDeclaration { const stmt: FunctionDeclaration = new FunctionDeclaration(); stmt.range = range; let i: i32, k: i32; @@ -975,10 +990,11 @@ export abstract class Statement extends Node { for (i = 0, k = (stmt.parameters = parameters).length; i < k; ++i) parameters[i].parent = stmt; if (stmt.returnType = returnType) (returnType).parent = stmt; if (stmt.statements = statements) for (i = 0, k = (statements).length; i < k; ++i) (statements)[i].parent = stmt; + for (i = 0, k = (stmt.decorators = decorators).length; i < k; ++i) decorators[i].parent = stmt; return stmt; } - static createMethod(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], parameters: Parameter[], returnType: TypeNode | null, statements: Statement[] | null, range: Range): MethodDeclaration { + static createMethod(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], parameters: Parameter[], returnType: TypeNode | null, statements: Statement[] | null, decorators: DecoratorStatement[], range: Range): MethodDeclaration { const stmt: MethodDeclaration = new MethodDeclaration(); stmt.range = range; let i: i32, k: i32; @@ -988,6 +1004,7 @@ export abstract class Statement extends Node { for (i = 0, k = (stmt.parameters = parameters).length; i < k; ++i) parameters[i].parent = stmt; if (stmt.returnType = returnType) (returnType).parent = stmt;; if (stmt.statements = statements) for (i = 0, k = (statements).length; i < k; ++i) (statements)[i].parent = stmt; + for (i = 0, k = (stmt.decorators = decorators).length; i < k; ++i) decorators[i].parent = stmt; return stmt; } @@ -1053,7 +1070,7 @@ export abstract class Statement extends Node { 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 = declarations).length; i < k; ++i) declarations[i].parent = stmt; + for (i = 0, k = (stmt.declarations = declarations).length; i < k; ++i) declarations[i].parent = stmt; return stmt; } @@ -1075,13 +1092,30 @@ export abstract class Statement extends Node { } } -export abstract class SourceNode extends Node { // extended by Source +export class Source extends Node { kind = NodeKind.SOURCE; parent = null; path: string; + normalizedPath: string; statements: Statement[]; + text: string; + tokenizer: Tokenizer | null = null; + isEntry: bool; + + constructor(path: string, text: string, isEntry: bool) { + super(); + this.path = path; + this.normalizedPath = normalizePath(path, true); + this.statements = new Array(); + this.range = new Range(this, 0, text.length); + this.text = text; + this.isEntry = isEntry; + } + + 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) { const statement: Statement = this.statements[i]; @@ -1097,7 +1131,6 @@ export abstract class SourceNode extends Node { // extended by Source export abstract class DeclarationStatement extends Statement { identifier: IdentifierExpression; - reflectionIndex: i32 = -1; } export class BlockStatement extends Statement { @@ -1107,9 +1140,9 @@ export class BlockStatement extends Statement { serialize(sb: string[]): void { sb.push("{\n"); - for (let i: i32 = 0; i < this.statements.length; ++i) { + for (let i: i32 = 0, k: i32 = this.statements.length; i < k; ++i) { this.statements[i].serialize(sb); - if (endsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(CharCode.CLOSEBRACE, sb)) sb.push("\n"); else sb.push(";\n"); @@ -1140,9 +1173,15 @@ export class ClassDeclaration extends DeclarationStatement { extendsType: TypeNode | null; implementsTypes: TypeNode[]; members: DeclarationStatement[]; + decorators: DecoratorStatement[]; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.decorators.length; i < k; ++i) { + 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(" "); } @@ -1150,7 +1189,7 @@ export class ClassDeclaration extends DeclarationStatement { sb.push(this.identifier.name); if (this.typeParameters.length) { sb.push("<"); - for (let i: i32 = 0; i < this.typeParameters.length; ++i) { + for (i = 0, k = this.typeParameters.length; i < k; ++i) { if (i > 0) sb.push(", "); this.typeParameters[i].serialize(sb); @@ -1163,16 +1202,16 @@ export class ClassDeclaration extends DeclarationStatement { } if (this.implementsTypes.length) { sb.push(" implements "); - for (let i: i32 = 0; i < this.implementsTypes.length; ++i) { + for (i = 0, k = this.implementsTypes.length; i < k; ++i) { if (i > 0) sb.push(", "); this.implementsTypes[i].serialize(sb); } } sb.push(" {\n"); - for (let i: i32 = 0; i < this.members.length; ++i) { + for (i = 0, k = this.members.length; i < k; ++i) { this.members[i].serialize(sb); - if (endsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(CharCode.CLOSEBRACE, sb)) sb.push("\n"); else sb.push(";\n"); @@ -1195,6 +1234,25 @@ export class ContinueStatement extends Statement { } } +export class DecoratorStatement extends Statement { + + kind = NodeKind.DECORATOR; + expression: Expression; + arguments: Expression[]; + + serialize(sb: string[]): void { + sb.push("@"); + this.expression.serialize(sb); + sb.push("("); + for (let i: i32 = 0, k: i32 = this.arguments.length; i < k; ++i) { + if (i > 0) + sb.push(", "); + this.arguments[i].serialize(sb); + } + sb.push(")"); + } +} + export class DoStatement extends Statement { kind = NodeKind.DO; @@ -1227,14 +1285,15 @@ export class EnumDeclaration extends DeclarationStatement { members: EnumValueDeclaration[]; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + 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"); - for (let i: i32 = 0; i < this.members.length; ++i) { + for (i = 0, k = this.members.length; i < k; ++i) { if (i > 0) sb.push(",\n"); this.members[i].serialize(sb); @@ -1292,14 +1351,16 @@ export class ExportStatement extends Statement { modifiers: Modifier[]; members: ExportMember[]; path: string | null; + normalizedPath: string | null; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.modifiers.length; i < k; ++i) { this.modifiers[i].serialize(sb); sb.push(" "); } sb.push("export {\n"); - for (let i: i32 = 0; i < this.members.length; ++i) { + for (i = 0, k = this.members.length; i < k; ++i) { if (i > 0) sb.push(",\n"); this.members[i].serialize(sb); @@ -1329,9 +1390,15 @@ export class FieldDeclaration extends DeclarationStatement { modifiers: Modifier[]; type: TypeNode | null; initializer: Expression | null; + decorators: DecoratorStatement[]; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.decorators.length; i < k; ++i) { + 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(" "); } @@ -1382,9 +1449,15 @@ export class FunctionDeclaration extends DeclarationStatement { parameters: Parameter[]; returnType: TypeNode | null; statements: Statement[] | null; + decorators: DecoratorStatement[]; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.decorators.length; i < k; ++i) { + 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(" "); } @@ -1394,9 +1467,10 @@ export class FunctionDeclaration extends DeclarationStatement { protected serializeCommon(sb: string[]): void { this.identifier.serialize(sb); + let i: i32, k: i32; if (this.typeParameters.length) { sb.push("<"); - for (let i: i32 = 0; i < this.typeParameters.length; ++i) { + for (i = 0, k = this.typeParameters.length; i < k; ++i) { if (i > 0) sb.push(", "); this.typeParameters[i].serialize(sb); @@ -1404,7 +1478,7 @@ export class FunctionDeclaration extends DeclarationStatement { sb.push(">"); } sb.push("("); - for (let i: i32 = 0; i < this.parameters.length; ++i) { + for (i = 0, k = this.parameters.length; i < k; ++i) { if (i > 0) sb.push(", "); this.parameters[i].serialize(sb); @@ -1416,10 +1490,10 @@ export class FunctionDeclaration extends DeclarationStatement { sb.push(")"); if (this.statements) { sb.push(" {\n"); - for (let i: i32 = 0; i < (this.statements).length; ++i) { + for (i = 0, k = (this.statements).length; i < k; ++i) { const statement: Statement = (this.statements)[i]; statement.serialize(sb); - if (endsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(CharCode.CLOSEBRACE, sb)) sb.push("\n"); else sb.push(";\n"); @@ -1472,10 +1546,11 @@ export class ImportStatement extends Statement { kind = NodeKind.IMPORT; declarations: ImportDeclaration[]; path: string; + normalizedPath: string; serialize(sb: string[]): void { sb.push("import {\n"); - for (let i: i32 = 0; i < this.declarations.length; ++i) { + for (let i: i32 = 0, k: i32 = this.declarations.length; i < k; ++i) { if (i > 0) sb.push(",\n"); this.declarations[i].serialize(sb); @@ -1494,7 +1569,8 @@ export class InterfaceDeclaration extends DeclarationStatement { members: Statement[]; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.modifiers.length; i < k; ++i) { this.modifiers[i].serialize(sb); sb.push(" "); } @@ -1502,7 +1578,7 @@ export class InterfaceDeclaration extends DeclarationStatement { this.identifier.serialize(sb); if (this.typeParameters.length) { sb.push("<"); - for (let i: i32 = 0; i < this.typeParameters.length; ++i) { + for (i = 0, k = this.typeParameters.length; i < k; ++i) { if (i > 0) sb.push(", "); this.typeParameters[i].serialize(sb); @@ -1514,9 +1590,9 @@ export class InterfaceDeclaration extends DeclarationStatement { (this.extendsType).serialize(sb); } sb.push(" {\n"); - for (let i: i32 = 0; i < this.members.length; ++i) { + for (i = 0, k = this.members.length; i < k; ++i) { this.members[i].serialize(sb); - if (endsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(CharCode.CLOSEBRACE, sb)) sb.push("\n"); else sb.push(";\n"); @@ -1530,7 +1606,12 @@ export class MethodDeclaration extends FunctionDeclaration { kind = NodeKind.METHOD; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.decorators.length; i < k; ++i) { + 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(" "); } @@ -1545,16 +1626,17 @@ export class NamespaceDeclaration extends DeclarationStatement { members: DeclarationStatement[]; serialize(sb: string[]): void { - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + let i: i32, k: i32; + 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"); - for (let i: i32 = 0; i < this.members.length; ++i) { + for (i = 0, k = this.members.length; i < k; ++i) { this.members[i].serialize(sb); - if (endsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(CharCode.CLOSEBRACE, sb)) sb.push("\n"); else sb.push(";\n"); @@ -1634,11 +1716,11 @@ export class SwitchCase extends Node { sb.push(":\n"); } else sb.push("default:\n"); - for (let i: i32 = 0; i < this.statements.length; ++i) { + for (let i: i32 = 0, k: i32 = this.statements.length; i < k; ++i) { if (i > 0) sb.push("\n"); this.statements[i].serialize(sb); - if (endsWith(CharCode.CLOSEBRACE, sb)) + if (builderEndsWith(CharCode.CLOSEBRACE, sb)) sb.push("\n"); else sb.push(";\n"); @@ -1656,7 +1738,7 @@ export class SwitchStatement extends Statement { sb.push("switch ("); this.expression.serialize(sb); sb.push(") {\n"); - for (let i: i32 = 0; i < this.cases.length; ++i) { + for (let i: i32 = 0, k: i32 = this.cases.length; i < k; ++i) { this.cases[i].serialize(sb); sb.push("\n"); } @@ -1684,14 +1766,15 @@ export class TryStatement extends Statement { serialize(sb: string[]): void { sb.push("try {\n"); - for (let i: i32 = 0; i < this.statements.length; ++i) { + let i: i32, k: i32; + for (i = 0, k = this.statements.length; i < k; ++i) { this.statements[i].serialize(sb); sb.push(";\n"); } sb.push("} catch ("); this.catchVariable.serialize(sb); sb.push(") {\n"); - for (let i: i32 = 0; i < this.catchStatements.length; ++i) { + for (i = 0, k = this.catchStatements.length; i < k; ++i) { this.catchStatements[i].serialize(sb); sb.push(";\n"); } @@ -1722,11 +1805,12 @@ export class VariableStatement extends Statement { kind = NodeKind.VARIABLE; modifiers: Modifier[]; - members: VariableDeclaration[]; + declarations: VariableDeclaration[]; serialize(sb: string[]): void { let isConst: bool = false; - for (let i: i32 = 0; i < this.modifiers.length; ++i) { + 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) @@ -1734,10 +1818,10 @@ export class VariableStatement extends Statement { } if (!isConst) sb.push("let "); - for (let i: i32 = 0; i < this.members.length; ++i) { + for (i = 0, k = this.declarations.length; i < k; ++i) { if (i > 0) sb.push(", "); - this.members[i].serialize(sb); + this.declarations[i].serialize(sb); } } } @@ -1768,12 +1852,12 @@ export function isDeclarationStatement(kind: NodeKind): bool { } export function serialize(node: Node, indent: i32 = 0): string { - sb.length = 0; + const sb: string[] = new Array(); // shared builder could grow too much node.serialize(sb); return sb.join(""); } -function endsWith(code: CharCode, sb: string[]): bool { +function builderEndsWith(code: CharCode, sb: string[]): 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.d.ts b/src/binaryen.d.ts index ef378188..23866c55 100644 --- a/src/binaryen.d.ts +++ b/src/binaryen.d.ts @@ -219,7 +219,7 @@ declare type BinaryenExportRef = usize; declare function _BinaryenAddExport(module: BinaryenModuleRef, internalName: CString, externalName: CString): BinaryenExportRef; declare function _BinaryenRemoveExport(module: BinaryenModuleRef, externalName: CString): void; -declare function _BinaryenAddGlobal(module: BinaryenModuleRef, name: CString, type: BinaryenType, mutable: i8, init: BinaryenExpressionRef): BinaryenImportRef; +declare function _BinaryenAddGlobal(module: BinaryenModuleRef, name: CString, type: BinaryenType, mutable: i8, init: BinaryenExpressionRef): BinaryenImportRef; // sic declare function _BinaryenSetFunctionTable(module: BinaryenModuleRef, funcs: CArray, numFuncs: BinaryenIndex): void; diff --git a/src/binaryen.ts b/src/binaryen.ts index c24e2564..f8899585 100644 --- a/src/binaryen.ts +++ b/src/binaryen.ts @@ -394,7 +394,7 @@ export class Module { } } - addFunction(name: string, type: Type, varTypes: Type[], body: BinaryenExpressionRef): BinaryenFunctionRef { + addFunction(name: string, type: BinaryenFunctionTypeRef, varTypes: Type[], body: BinaryenExpressionRef): BinaryenFunctionRef { const cStr: CString = allocString(name); const cArr: CArray = allocI32Array(varTypes); try { diff --git a/src/compiler.ts b/src/compiler.ts index 48a55981..9b95c492 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -2,23 +2,28 @@ import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp, Type as BinaryenType import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { hasModifier } from "./parser"; import { Program } from "./program"; -import { CharCode, I64, U64 } from "./util"; +import { CharCode, I64, U64, normalizePath, sb } from "./util"; import { Token } from "./tokenizer"; import { NodeKind, TypeNode, + Source, // statements BlockStatement, BreakStatement, ClassDeclaration, ContinueStatement, + DeclarationStatement, DoStatement, EmptyStatement, EnumDeclaration, EnumValueDeclaration, + ExportMember, + ExportStatement, ExpressionStatement, + FieldDeclaration, FunctionDeclaration, ForStatement, IfStatement, @@ -58,19 +63,12 @@ import { } from "./ast"; import { - Enum, - Class, - Field, - Function, - GlobalVariable, - LocalVariable, - Namespace, - Method, - Source, + ClassType, + FunctionType, Type, TypeKind -} from "./reflection"; +} from "./types"; export enum Target { WASM32, @@ -79,6 +77,7 @@ export enum Target { export class Options { target: Target = Target.WASM32; + noEmit: bool = false; } export class Compiler extends DiagnosticEmitter { @@ -87,16 +86,20 @@ export class Compiler extends DiagnosticEmitter { options: Options; module: Module; + startFunction: FunctionType = new FunctionType(Type.void, new Array()); + startFunctionBody: BinaryenExpressionRef[] = new Array(); + currentType: Type = Type.void; - currentClass: Class | null = null; - currentFunction: Function | null = null; - breakMajor: i32 = 0; - breakMinor: i32 = 0; + currentClass: ClassType | null = null; + currentFunction: FunctionType = this.startFunction; memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL memorySegments: MemorySegment[] = new Array(); - statements: BinaryenExpressionRef[] = new Array(); // TODO: make start function + classes: Map = new Map(); + enums: Set = new Set(); + functions: Map = new Map(); + globals: Set = new Set(); static compile(program: Program, options: Options | null = null): Module { const compiler: Compiler = new Compiler(program, options); @@ -123,62 +126,61 @@ export class Compiler extends DiagnosticEmitter { switch (statement.kind) { case NodeKind.CLASS: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers) && !(statement).typeParameters.length) { - const cl: Class = Class.create(statement, []).exportAs((statement).identifier.name); - this.program.addClass(cl); - this.compileClass(cl); - } + if (hasModifier(ModifierKind.EXPORT, (statement).modifiers) && !(statement).typeParameters.length) + this.compileClass(statement, []); break; case NodeKind.ENUM: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) { - const en: Enum = Enum.create(statement).exportAs((statement).identifier.name); - this.program.addEnum(en); - this.compileEnum(en); - } + if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) + this.compileEnum(statement); break; case NodeKind.FUNCTION: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers) && !(statement).typeParameters.length) { - const fn: Function = Function.create(statement, []).exportAs((statement).identifier.name); - this.program.addFunction(fn); - this.compileFunction(fn); - } + 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)) { - const ns: Namespace = Namespace.create(statement).exportAs((statement).identifier.name); - this.program.addNamespace(ns); - this.compileNamespace(ns); - } + if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) + this.compileNamespace(statement); break; case NodeKind.VARIABLE: - if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) { - const gls: GlobalVariable[] = GlobalVariable.create(statement); - for (let j: i32 = 0, l: i32 = gls.length; j < l; ++j) { - const gl: GlobalVariable = gls[j]; - gl.exportName = (gl.declaration).identifier.name; // WASM can't do this right now - this.program.addGlobal(gl); - this.compileGlobal(gl); - } - } + if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) + this.compileGlobals(statement); break; - case NodeKind.EXPORT: - // TODO: obtain referenced declaration and export that + case NodeKind.EXPORT: { + this.compileExports(statement); break; + } - default: - this.statements.push(this.compileStatement(statement)); + default: { + const previousClass: ClassType | null = this.currentClass; + const previousFunction: FunctionType = this.currentFunction; + this.currentClass = null; + this.currentFunction = this.startFunction; + this.startFunctionBody.push(this.compileStatement(statement)); + this.currentClass = previousClass; + this.currentFunction = previousFunction; break; + } } } + // make start function if not empty + if (this.startFunctionBody.length) { + let typeRef: BinaryenFunctionTypeRef = this.module.getFunctionTypeBySignature(BinaryenType.None, []); + if (!typeRef) + typeRef = this.module.addFunctionType("v", BinaryenType.None, []); + this.module.setStart( + this.module.addFunction(".start", typeRef, [], this.module.createBlock("", this.startFunctionBody)) + ); + } + // set up memory size and static segments const initial: U64 = this.memoryOffset.clone(); const initialOverlaps: U64 = initial.clone(); @@ -193,34 +195,115 @@ export class Compiler extends DiagnosticEmitter { return this.module; } - compileClass(cl: Class): void { + compileClass(declaration: ClassDeclaration, typeArguments: Type[]): void { throw new Error("not implemented"); } - compileEnum(en: Enum): void { - for (let i: i32 = 0, k = en.declaration.members.length; i < k; ++i) { - const declaration: EnumValueDeclaration = en.declaration.members[i]; - const name: string = this.program.mangleName(declaration); - const value: BinaryenExpressionRef = declaration.value - ? this.compileExpression(declaration.value, Type.i32) - : this.module.createI32(i); // TODO - this.module.addGlobal(name, BinaryenType.I32, false, value); - if (en.exportName != null) { - // TODO: WASM does not yet support non-function exports - } + 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); + return; } + const valueDeclarations: EnumValueDeclaration[] = declaration.members; + let previousValueName: string | null = null; + for (let i: i32 = 0, k: i32 = valueDeclarations.length; i < k; ++i) + previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName); + this.enums.add(name); } - compileFunction(fn: Function): void { + compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null): 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); + } + 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(); + let binaryenTypeRef: BinaryenFunctionTypeRef = this.module.getFunctionTypeBySignature(binaryenResultType, binaryenParamTypes); + if (!binaryenTypeRef) + binaryenTypeRef = this.module.addFunctionType("", binaryenResultType, binaryenParamTypes); */ throw new Error("not implemented"); } - compileGlobal(gl: GlobalVariable): void { + compileGlobals(statement: VariableStatement): void { + const declarations: VariableDeclaration[] = statement.declarations; + const isConst: bool = hasModifier(ModifierKind.CONST, statement.modifiers); + for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) + this.compileGlobal(declarations[i], isConst); + } + + 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); + return; + } + if (!this.options.noEmit) { + const binaryenType: BinaryenType = typeToBinaryenType(type); + const initializer: BinaryenExpressionRef = declaration.initializer ? this.compileExpression(declaration.initializer, type) : typeToBinaryenZero(this.module, type); + this.module.addGlobal(name, binaryenType, !isConst, initializer); + } + this.globals.add(name); + } + + compileNamespace(declaration: NamespaceDeclaration): void { throw new Error("not implemented"); } - compileNamespace(ns: Namespace): void { - throw new Error("not implemented"); + compileExports(statement: ExportStatement): void { + const members: ExportMember[] = statement.members; + const normalizedPath: string | null = statement.path == null ? null : normalizePath(statement.path); + 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); + if (declaration) { + switch ((declaration).kind) { + + case NodeKind.CLASS: + if (!(declaration).typeParameters.length) + this.compileClass(declaration, []); + break; + + case NodeKind.ENUM: + this.compileEnum(declaration); + break; + + case NodeKind.FUNCTION: + if (!(declaration).typeParameters.length) + this.compileFunction(declaration, []); + break; + + case NodeKind.NAMESPACE: + this.compileNamespace(declaration); + break; + + case NodeKind.VARIABLEDECLARATION: + this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, (declaration.parent).modifiers)); + break; + + default: + throw new Error("unexpected declaration kind"); + } + } else + throw new Error("unexpected missing declaration"); } // memory @@ -238,31 +321,23 @@ export class Compiler extends DiagnosticEmitter { // types - resolveType(node: TypeNode, reportNotFound: bool = true): Type | null { + 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 const types: Map = this.program.types; - const name: string = node.identifier.name; - if (types.has(name)) { - const type: Type = types.get(name); - return type; - } + const globalName: string = node.identifier.name; + const localName: string = node.range.source.normalizedPath + "/" + globalName; + if (types.has(localName)) + return types.get(localName); + if (types.has(globalName)) + return types.get(globalName); if (reportNotFound) - this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, name); + this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName); return null; } // statements - enterBreakContext(): string { - if (this.breakMinor == 0) - ++this.breakMajor; - ++this.breakMinor; - return this.breakMajor.toString() + "." + this.breakMinor.toString(); - } - - leaveBreakContext(): void { - --this.breakMinor; - } - compileStatement(statement: Statement): BinaryenExpressionRef { switch (statement.kind) { @@ -355,7 +430,7 @@ export class Compiler extends DiagnosticEmitter { compileReturnStatement(statement: ReturnStatement): BinaryenExpressionRef { if (this.currentFunction) { - const expression: BinaryenExpressionRef = statement.expression ? this.compileExpression(statement.expression, (this.currentFunction).returnType) : 0; + const expression: BinaryenExpressionRef = statement.expression ? this.compileExpression(statement.expression, this.currentFunction.returnType) : 0; return this.module.createReturn(expression); } return this.module.createUnreachable(); @@ -379,11 +454,11 @@ export class Compiler extends DiagnosticEmitter { compileWhileStatement(statement: WhileStatement): BinaryenExpressionRef { const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32); - const label: string = this.enterBreakContext(); + const label: string = this.currentFunction.enterBreakContext(); const breakLabel: string = "break$" + label; const continueLabel: string = "continue$" + label; const body: BinaryenExpressionRef = this.compileStatement(statement.statement); - this.leaveBreakContext(); + this.currentFunction.leaveBreakContext(); return this.module.createBlock(breakLabel, [ this.module.createLoop(continueLabel, this.module.createIf(condition, this.module.createBlock("", [ @@ -616,7 +691,7 @@ export class Compiler extends DiagnosticEmitter { } compileAssertionExpression(expression: AssertionExpression, contextualType: Type): BinaryenExpressionRef { - const toType: Type | null = this.resolveType(expression.toType); // reports + const toType: Type | null = this.resolveType(expression.toType, this.currentFunction.typeArgumentsMap); // reports if (toType && toType != contextualType) { const expr: BinaryenExpressionRef = this.compileExpression(expression.expression, toType); return this.convertExpression(expr, this.currentType, toType); @@ -835,11 +910,11 @@ export class Compiler extends DiagnosticEmitter { } return compound - ? this.compileAssignment(expression.left, this.module.createBinary(op, left, right)) + ? this.compileAssignment(expression.left, this.module.createBinary(op, left, right), this.currentType != Type.void) : this.module.createBinary(op, left, right); } - compileAssignment(expression: Expression, value: BinaryenExpressionRef): BinaryenExpressionRef { + compileAssignment(expression: Expression, value: BinaryenExpressionRef, tee: bool = false): BinaryenExpressionRef { throw new Error("not implemented"); } @@ -908,18 +983,37 @@ export class Compiler extends DiagnosticEmitter { } compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): BinaryenExpressionRef { - let operand: BinaryenExpressionRef; - let op: UnaryOp; + const operator: Token = expression.operator; - switch (expression.operator) { + let op: BinaryOp; + let binaryenType: BinaryenType; + let binaryenOne: BinaryenExpressionRef; - case Token.PLUS_PLUS: - case Token.MINUS_MINUS: - - default: - throw new Error("not implemented"); + if (contextualType == Type.f32) { + op = operator == Token.PLUS_PLUS ? BinaryOp.AddF32 : BinaryOp.SubF32; + binaryenType = BinaryenType.F32; + binaryenOne = this.module.createF32(1); + } else if (contextualType == Type.f64) { + op = operator == Token.PLUS_PLUS ? BinaryOp.AddF64 : BinaryOp.SubF64; + binaryenType = BinaryenType.F64; + binaryenOne = this.module.createF64(1); + } else if (contextualType.isLongInteger) { + op = operator == Token.PLUS_PLUS ? BinaryOp.AddI64 : BinaryOp.SubI64; + binaryenType = BinaryenType.I64; + binaryenOne = this.module.createI64(1, 0); + } else { + op = operator == Token.PLUS_PLUS ? BinaryOp.AddI32 : BinaryOp.SubI32; + binaryenType = BinaryenType.I32; + binaryenOne = this.module.createI32(1); } - // return this.module.createBinary(op, operand); + + const getValue: BinaryenExpressionRef = this.compileExpression(expression.expression, contextualType); + const setValue: BinaryenExpressionRef = this.compileAssignment(expression.expression, this.module.createBinary(op, getValue, binaryenOne), false); // reports + + return this.module.createBlock(null, [ + getValue, + setValue + ], binaryenType); } compileUnaryPrefixExpression(expression: UnaryPrefixExpression, contextualType: Type): BinaryenExpressionRef { @@ -929,7 +1023,7 @@ export class Compiler extends DiagnosticEmitter { switch (expression.operator) { case Token.PLUS: - return this.compileExpression(expression.expression, contextualType); + return this.compileExpression(expression.expression, contextualType); // noop case Token.MINUS: operand = this.compileExpression(expression.expression, contextualType); @@ -939,12 +1033,29 @@ export class Compiler extends DiagnosticEmitter { op = UnaryOp.NegF64; else return this.currentType.isLongInteger - ? this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0, 0), operand) - : this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), operand); + ? this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0, 0), operand) + : this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), operand); break; - // case Token.PLUS_PLUS: - // case Token.MINUS_MINUS: + case Token.PLUS_PLUS: + operand = this.compileExpression(expression.expression, contextualType); + return this.currentType == Type.f32 + ? this.compileAssignment(expression.expression, 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.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); + + case Token.MINUS_MINUS: + operand = this.compileExpression(expression.expression, contextualType); + return this.currentType == Type.f32 + ? this.compileAssignment(expression.expression, 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.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); case Token.EXCLAMATION: operand = this.compileExpression(expression.expression, Type.bool, false); @@ -965,8 +1076,8 @@ export class Compiler extends DiagnosticEmitter { case Token.TILDE: operand = this.compileExpression(expression.expression, 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)); + ? this.module.createBinary(BinaryOp.XorI64, operand, this.module.createI64(-1, -1)) + : this.module.createBinary(BinaryOp.XorI32, operand, this.module.createI32(-1)); default: throw new Error("not implemented"); @@ -975,3 +1086,57 @@ export class Compiler extends DiagnosticEmitter { return this.module.createUnary(op, operand); } } + +// helpers + +function typeToBinaryenType(type: Type): BinaryenType { + return type.kind == TypeKind.F32 + ? BinaryenType.F32 + : type.kind == TypeKind.F64 + ? BinaryenType.F64 + : type.isLongInteger + ? BinaryenType.I64 + : type.isAnyInteger || type.kind == TypeKind.BOOL + ? BinaryenType.I32 + : BinaryenType.None; +} + +function typeToBinaryenZero(module: Module, type: Type): BinaryenExpressionRef { + return type.kind == TypeKind.F32 + ? module.createF32(0) + : type.kind == TypeKind.F64 + ? module.createF64(0) + : type.isLongInteger + ? module.createI64(0, 0) + : module.createI32(0); +} + +function typeToBinaryenOne(module: Module, type: Type): BinaryenExpressionRef { + return type.kind == TypeKind.F32 + ? module.createF32(1) + : type.kind == TypeKind.F64 + ? module.createF64(1) + : type.isLongInteger + ? module.createI64(1, 0) + : module.createI32(1); +} + +function typeToSignatureNamePart(type: Type): string { + return type.kind == TypeKind.VOID + ? "v" + : type.kind == TypeKind.F32 + ? "f" + : type.kind == TypeKind.F64 + ? "F" + : type.isLongInteger + ? "I" + : "i"; +} + +function typesToSignatureName(paramTypes: Type[], returnType: Type): string { + sb.length = 0; + for (let i: i32 = 0, k = 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 caaec340..9b4f6257 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -39,12 +39,14 @@ export enum DiagnosticCode { An_implementation_cannot_be_declared_in_ambient_contexts = 1183, An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive = 1198, Unterminated_Unicode_escape_sequence = 1199, + Decorators_are_not_valid_here = 1206, _abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration = 1242, Duplicate_identifier_0 = 2300, Cannot_find_name_0 = 2304, Generic_type_0_requires_1_type_argument_s = 2314, Type_0_is_not_generic = 2315, Type_0_is_not_assignable_to_type_1 = 2322, + 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 } @@ -88,12 +90,14 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1183: return "An implementation cannot be declared in ambient contexts."; case 1198: return "An extended Unicode escape value must be between 0x0 and 0x10FFFF inclusive."; case 1199: return "Unterminated Unicode escape sequence."; + case 1206: return "Decorators are not valid here."; case 1242: return "'abstract' modifier can only appear on a class, method, or property declaration."; case 2300: return "Duplicate identifier '{0}'."; case 2304: return "Cannot find name '{0}'."; 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 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."; default: return ""; } diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 518c79dc..ccf2b8ed 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -38,6 +38,7 @@ "An implementation cannot be declared in ambient contexts.": 1183, "An extended Unicode escape value must be between 0x0 and 0x10FFFF inclusive.": 1198, "Unterminated Unicode escape sequence.": 1199, + "Decorators are not valid here.": 1206, "'abstract' modifier can only appear on a class, method, or property declaration.": 1242, "Duplicate identifier '{0}'.": 2300, @@ -45,5 +46,6 @@ "Generic type '{0}' requires {1} type argument(s).": 2314, "Type '{0}' is not generic.": 2315, "Type '{0}' is not assignable to type '{1}'.": 2322, + "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 } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index b70b8891..0dcb34df 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,5 +1,5 @@ import { Range } from "./ast"; -import { CharCode, isLineBreak, sb } from "./util"; +import { isLineBreak, sb } from "./util"; import { DiagnosticCode, diagnosticCodeToString } from "./diagnosticMessages.generated"; export { DiagnosticCode, diagnosticCodeToString } from "./diagnosticMessages.generated"; diff --git a/src/index.ts b/src/index.ts index 769b0820..97933ea5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,7 @@ import { Module } from "./binaryen"; import { Compiler } from "./compiler"; -import { DiagnosticMessage, DiagnosticCategory, DiagnosticCode } from "./diagnostics"; +import { DiagnosticMessage, DiagnosticCategory } from "./diagnostics"; import { Parser } from "./parser"; import { Program } from "./program"; diff --git a/src/parser.ts b/src/parser.ts index cb971a7b..08aa062d 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -8,30 +8,26 @@ */ import { Program } from "./program"; -import { Source } from "./reflection"; import { Tokenizer, Token, Range } from "./tokenizer"; -import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, formatDiagnosticMessage } from "./diagnostics"; -import { I64, normalizePath, resolvePath } from "./util"; +import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics"; +import { normalizePath } from "./util"; import { NodeKind, + Source, // types TypeNode, // expressions - AssertionExpression, AssertionKind, Expression, - FloatLiteralExpression, IdentifierExpression, - IntegerLiteralExpression, - LiteralExpression, - LiteralKind, // statements BlockStatement, ClassDeclaration, + DecoratorStatement, DoStatement, EnumDeclaration, EnumValueDeclaration, @@ -79,9 +75,7 @@ export class Parser extends DiagnosticEmitter { for (let i: i32 = 0, k: i32 = this.program.sources.length; i < k; ++i) if (this.program.sources[i].normalizedPath == normalizedPath) throw Error("duplicate source"); - - if (isEntry) - this.seenlog.add(normalizedPath); + this.seenlog.add(normalizedPath); const source: Source = new Source(path, text, isEntry); this.program.sources.push(source); @@ -91,6 +85,17 @@ export class Parser extends DiagnosticEmitter { while (!tn.skip(Token.ENDOFFILE)) { + let decorators: DecoratorStatement[] | null = null; + + while (tn.skip(Token.AT)) { + const decorator: DecoratorStatement | null = this.parseDecorator(tn); + if (!decorator) + break; + if (!decorators) + decorators = new Array(); + (decorators).push(decorator); + } + let modifiers: Modifier[] | null = null; if (tn.skip(Token.EXPORT)) @@ -127,7 +132,8 @@ export class Parser extends DiagnosticEmitter { break; case Token.FUNCTION: - statement = this.parseFunction(tn, modifiers ? modifiers : createModifiers()); + statement = this.parseFunction(tn, modifiers ? modifiers : createModifiers(), decorators); + decorators = null; break; case Token.ABSTRACT: @@ -139,7 +145,8 @@ export class Parser extends DiagnosticEmitter { // fall through case Token.CLASS: - statement = this.parseClass(tn, modifiers ? modifiers : createModifiers()); + statement = this.parseClass(tn, modifiers ? modifiers : createModifiers(), decorators); + decorators = null; break; case Token.IMPORT: @@ -170,6 +177,9 @@ export class Parser extends DiagnosticEmitter { break; } + if (decorators) + for (let i: i32 = 0, k: i32 = (decorators).length; i < k; ++i) + this.error(DiagnosticCode.Decorators_are_not_valid_here, (decorators)[i].range); if (!statement) return; statement.parent = source; @@ -270,12 +280,12 @@ export class Parser extends DiagnosticEmitter { } // ... [][] while (tn.skip(Token.OPENBRACKET)) { - let bracketRange: Range = tn.range(); + let bracketStart: i32 = tn.tokenPos; if (!tn.skip(Token.CLOSEBRACKET)) { this.error(DiagnosticCode._0_expected, tn.range(), "]"); return null; } - bracketRange = Range.join(bracketRange, tn.range()); + const bracketRange = tn.range(bracketStart, tn.pos); // ...[] | null let nullable: bool = false; @@ -297,6 +307,32 @@ export class Parser extends DiagnosticEmitter { // statements + parseDecorator(tn: Tokenizer): DecoratorStatement | null { + // at '@': Identifier ('.' Identifier)* '(' Arguments + const startPos: i32 = tn.tokenPos; + if (tn.skip(Token.IDENTIFIER)) { + let name: string = tn.readIdentifier(); + let expression: Expression = Expression.createIdentifier(name, tn.range(startPos, tn.pos)); + while (tn.skip(Token.DOT)) { + if (tn.skip(Token.IDENTIFIER)) { + name = tn.readIdentifier(); + expression = Expression.createPropertyAccess(expression, Expression.createIdentifier(name, tn.range()), tn.range(startPos, tn.pos)); + } else { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + } + if (tn.skip(Token.OPENPAREN)) { + const args: Expression[] | null = this.parseArguments(tn); + if (args) + return Statement.createDecorator(expression, args, tn.range(startPos, tn.pos)); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + parseVariable(tn: Tokenizer, modifiers: Modifier[]): VariableStatement | null { // at ('const' | 'let' | 'var'): VariableDeclaration (',' VariableDeclaration)* ';'? const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; @@ -481,9 +517,14 @@ export class Parser extends DiagnosticEmitter { return null; } - parseFunction(tn: Tokenizer, modifiers: Modifier[]): FunctionDeclaration | null { + parseFunction(tn: Tokenizer, modifiers: Modifier[], decorators: DecoratorStatement[] | null): FunctionDeclaration | null { // at 'function': Identifier ('<' TypeParameters)? '(' Parameters (':' Type)? '{' Statement* '}' ';'? - const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; + const startPos: i32 = decorators && (decorators).length + ? (decorators)[0].range.start + : modifiers.length + ? modifiers[0].range.start + : tn.tokenPos; + if (!tn.skip(Token.IDENTIFIER)) { this.error(DiagnosticCode.Identifier_expected, tn.range(tn.pos)); return null; @@ -524,14 +565,18 @@ 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, tn.range(startPos, tn.pos)); + const ret: FunctionDeclaration = Statement.createFunction(modifiers, identifier, typeParameters, parameters, returnType, statements, decorators ? decorators : new Array(0), tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } - parseClass(tn: Tokenizer, modifiers: Modifier[]): ClassDeclaration | null { + parseClass(tn: Tokenizer, modifiers: Modifier[], decorators: DecoratorStatement[] | null): ClassDeclaration | null { // at 'class': Identifier ('<' TypeParameters)? ('extends' Type)? ('implements' Type (',' Type)*)? '{' ClassMember* '}' - const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; + const startPos: i32 = decorators && (decorators).length + ? (decorators)[0].range.start + : modifiers.length + ? modifiers[0].range.start + : tn.tokenPos; if (tn.skip(Token.IDENTIFIER)) { const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); @@ -572,7 +617,7 @@ export class Parser extends DiagnosticEmitter { members.push(member); } while (!tn.skip(Token.CLOSEBRACE)); } - return Statement.createClass(modifiers, identifier, typeParameters, extendsType, implementsTypes, members, tn.range(startPos, tn.pos)); + return Statement.createClass(modifiers, identifier, typeParameters, extendsType, implementsTypes, members, decorators ? decorators : new Array(0), tn.range(startPos, tn.pos)); } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); } else @@ -584,6 +629,15 @@ export class Parser extends DiagnosticEmitter { // ('public' | 'private' | 'protected')? ('static' | 'abstract')? ('get' | 'set')? Identifier ... const startRange: Range = tn.range(); + let decorators: DecoratorStatement[] = new Array(); + + while (tn.skip(Token.AT)) { + const decorator: DecoratorStatement | null = this.parseDecorator(tn); + if (!decorator) + break; + decorators.push(decorator); + } + let modifiers: Modifier[] | null = null; if (tn.skip(Token.PUBLIC)) @@ -641,7 +695,7 @@ export class Parser extends DiagnosticEmitter { this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, tn.range()); // recoverable } - const ret: MethodDeclaration = Statement.createMethod(modifiers ? modifiers : createModifiers(), identifier, typeParameters, parameters, returnType, statements, Range.join(startRange, tn.range())); + const ret: MethodDeclaration = Statement.createMethod(modifiers ? modifiers : createModifiers(), identifier, typeParameters, parameters, returnType, statements, decorators, Range.join(startRange, tn.range())); tn.skip(Token.SEMICOLON); return ret; @@ -666,7 +720,7 @@ export class Parser extends DiagnosticEmitter { if (!initializer) return null; } - const ret: FieldDeclaration = Statement.createField(modifiers ? modifiers : createModifiers(), identifier, type, initializer, Range.join(startRange, tn.range())); + const ret: FieldDeclaration = Statement.createField(modifiers ? modifiers : createModifiers(), identifier, type, initializer, decorators, Range.join(startRange, tn.range())); tn.skip(Token.SEMICOLON); return ret; } @@ -694,19 +748,18 @@ export class Parser extends DiagnosticEmitter { } let path: string | null = null; if (tn.skip(Token.FROM)) { - if (tn.skip(Token.STRINGLITERAL)) { + if (tn.skip(Token.STRINGLITERAL)) path = tn.readString(); - const resolvedPath: string = resolvePath(normalizePath(path), tn.source.normalizedPath); - if (!this.seenlog.has(resolvedPath)) { - this.backlog.push(resolvedPath); - this.seenlog.add(resolvedPath); - } - } else { + else { this.error(DiagnosticCode.String_literal_expected, tn.range()); return null; } } const ret: ExportStatement = Statement.createExport(modifiers, members, path, Range.join(startRange, tn.range())); + if (ret.normalizedPath && !this.seenlog.has(ret.normalizedPath)) { + this.backlog.push(ret.normalizedPath); + this.seenlog.add(ret.normalizedPath); + } tn.skip(Token.SEMICOLON); return ret; } else @@ -753,12 +806,11 @@ export class Parser extends DiagnosticEmitter { if (tn.skip(Token.FROM)) { if (tn.skip(Token.STRINGLITERAL)) { const path: string = tn.readString(); - const resolvedPath: string = resolvePath(normalizePath(path), tn.source.normalizedPath); - if (!this.seenlog.has(resolvedPath)) { - this.backlog.push(resolvedPath); - this.seenlog.add(resolvedPath); - } const ret: ImportStatement = Statement.createImport(members, path, Range.join(startRange, tn.range())); + if (!this.seenlog.has(ret.normalizedPath)) { + this.backlog.push(ret.normalizedPath); + this.seenlog.add(ret.normalizedPath); + } tn.skip(Token.SEMICOLON); return ret; } else @@ -1153,6 +1205,8 @@ 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); return Expression.createUnaryPrefix(token, operand, tn.range(startPos, tn.pos)); } @@ -1320,6 +1374,8 @@ export class Parser extends DiagnosticEmitter { // UnaryPostfixExpression } else if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) { + if (expr.kind != NodeKind.IDENTIFIER && expr.kind != NodeKind.ELEMENTACCESS && expr.kind != NodeKind.PROPERTYACCESS) + this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, expr.range); expr = Expression.createUnaryPostfix(token, expr, tn.range(startPos, tn.pos)); // SelectExpression diff --git a/src/program.ts b/src/program.ts index 63550126..e5e5ae58 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,15 +1,13 @@ import { Target } from "./compiler"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; -import { Source, Type, Class, Enum, Function, GlobalVariable, Namespace } from "./reflection"; import { hasModifier } from "./parser"; -import { normalizePath, resolvePath, trimExtension } from "./util"; +import { Type } from "./types"; import { + ModifierKind, Node, NodeKind, - SourceNode, - ModifierKind, - IdentifierExpression, + Source, ClassDeclaration, DeclarationStatement, @@ -36,7 +34,7 @@ class QueuedExport { } class QueuedImport { - globalName: string; + internalName: string; importName: string; declaration: ImportDeclaration; } @@ -47,20 +45,13 @@ export class Program extends DiagnosticEmitter { diagnosticsOffset: i32 = 0; target: Target = Target.WASM32; - // internal names to declarations + /** Internal map of names to declarations. */ names: Map = new Map(); - // type names to types + /** Separate map of internal type names to declarations. */ types: Map = new Map(); - // export names to declarations (separate from internal names) + /** Separate map of internal export names to declarations. */ exports: Map = new Map(); - // reflection instances - classes: Class[] = new Array(); - enums: Enum[] = new Array(); - functions: Function[] = new Array(); - globals: GlobalVariable[] = new Array(); - namespaces: Namespace[] = new Array(); - constructor(diagnostics: DiagnosticMessage[] | null = null) { super(diagnostics); this.sources = new Array(); @@ -68,12 +59,13 @@ export class Program extends DiagnosticEmitter { initialize(target: Target): void { this.target = target; + initializeBasicTypes(this.types, target); - const exportsMap: Map = new Map(); - const importsQueue: QueuedImport[] = new Array(); + const queuedExports: Map = new Map(); + const queuedImports: QueuedImport[] = new Array(); - // build initial lookup maps of global and export names to declarations + // build initial lookup maps of internal and export 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; @@ -90,7 +82,7 @@ export class Program extends DiagnosticEmitter { break; case NodeKind.EXPORT: - this.initializeExports(statement, exportsMap); + this.initializeExports(statement, queuedExports); break; case NodeKind.FUNCTION: @@ -98,7 +90,7 @@ export class Program extends DiagnosticEmitter { break; case NodeKind.IMPORT: - this.initializeImports(statement, exportsMap, importsQueue); + this.initializeImports(statement, queuedExports, queuedImports); break; case NodeKind.INTERFACE: @@ -116,52 +108,53 @@ export class Program extends DiagnosticEmitter { } } - // at this point exports map should be resolvable - for (let [key, val] of exportsMap.entries()) { + // at this point queued exports should be resolvable + for (let [exportName, queuedExport] of queuedExports.entries()) { const seen: Set = new Set(); - while (exportsMap.has(val.importName)) { - val = exportsMap.get(val.importName); - if (seen.has(val)) + while (queuedExports.has(queuedExport.importName)) { + queuedExport = queuedExports.get(queuedExport.importName); + if (seen.has(queuedExport)) break; - seen.add(val); + seen.add(queuedExport); } - if (this.exports.has(val.importName)) - this.addExport(key, this.exports.get(val.importName)); + if (this.exports.has(queuedExport.importName)) + this.addExport(exportName, this.exports.get(queuedExport.importName)); else - this.error(DiagnosticCode.Cannot_find_name_0, val.member.externalIdentifier.range, val.importName); + this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.importName); } - // at this point imports queue should be resolvable - for (let i: i32 = 0, k: i32 = importsQueue.length; i < k; ++i) { - const queued: QueuedImport = importsQueue[i]; - const globalName: string = queued.globalName; - let importName: string = queued.importName; + // at this point also queued imports should be resolvable + 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(); - while (exportsMap.has(importName)) { - const val: QueuedExport = exportsMap.get(importName); - importName = val.importName; - if (seen.has(val)) + while (queuedExports.has(importName)) { + const queuedExport: QueuedExport = queuedExports.get(importName); + importName = queuedExport.importName; + if (seen.has(queuedExport)) break; - seen.add(val); + seen.add(queuedExport); } if (this.exports.has(importName)) { - if (this.names.has(globalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, queued.declaration.identifier.range, globalName); + if (this.names.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, queuedImport.declaration.identifier.range, internalName); else { const declaration: DeclarationStatement = this.exports.get(importName); - this.names.set(globalName, declaration); + this.names.set(internalName, declaration); // TODO: also mirror (non-private) member names? + // wouldn't it be better to look up members based on their parent? } } else - this.error(DiagnosticCode.Cannot_find_name_0, queued.declaration.externalIdentifier.range, importName); + this.error(DiagnosticCode.Cannot_find_name_0, queuedImport.declaration.externalIdentifier.range, importName); } } private initializeClass(declaration: ClassDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(globalName, declaration); + this.addExport(/* same as */internalName, declaration); const members: DeclarationStatement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -182,87 +175,91 @@ export class Program extends DiagnosticEmitter { } private initializeField(declaration: FieldDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); } private initializeEnum(declaration: EnumDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(globalName, declaration); + this.addExport(/* same as */internalName, declaration); const members: EnumValueDeclaration[] = declaration.members; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) this.initializeEnumValue(members[i]); } private initializeEnumValue(declaration: EnumValueDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); } - private initializeExports(statement: ExportStatement, exportsMap: Map): void { + private initializeExports(statement: ExportStatement, queuedExports: Map): void { const members: ExportMember[] = statement.members; - const normalizedPath: string | null = statement.path == null ? null : normalizePath(statement.path); for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) - this.initializeExport(members[i], normalizedPath, exportsMap); + this.initializeExport(members[i], statement.normalizedPath, queuedExports); } - private initializeExport(member: ExportMember, normalizedPath: string | null, exportsMap: Map): void { + private initializeExport(member: ExportMember, normalizedPath: string | null, queuedExports: Map): void { const exportName: string = member.range.source.normalizedPath + "/" + member.externalIdentifier.name; - if (exportsMap.has(exportName)) + if (queuedExports.has(exportName)) this.error(DiagnosticCode.Duplicate_identifier_0, member.externalIdentifier.range, exportName); else { - const queued: QueuedExport = new QueuedExport(); - queued.importName = normalizedPath == null + const queuedExport: QueuedExport = new QueuedExport(); + queuedExport.importName = normalizedPath == null ? member.range.source.normalizedPath + "/" + member.identifier.name // local : (normalizedPath) + "/" + member.identifier.name; // re-export - queued.member = member; - exportsMap.set(exportName, queued); + queuedExport.member = member; + queuedExports.set(exportName, queuedExport); } } private initializeFunction(declaration: FunctionDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(globalName, declaration); + this.addExport(/* same as */internalName, declaration); } - private initializeImports(statement: ImportStatement, exportsMap: Map, importsQueue: QueuedImport[]): void { + private initializeImports(statement: ImportStatement, queuedExports: Map, queuedImports: QueuedImport[]): void { const members: ImportDeclaration[] = statement.declarations; - const normalizedPath: string = normalizePath(statement.path); for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { const declaration: ImportDeclaration = members[i]; - this.initializeImport(declaration, normalizedPath, exportsMap, importsQueue); + this.initializeImport(declaration, statement.normalizedPath, queuedExports, queuedImports); } } - private initializeImport(declaration: ImportDeclaration, normalizedPath: string, exportsMap: Map, importsQueue: QueuedImport[]): void { + private initializeImport(declaration: ImportDeclaration, normalizedPath: string, queuedExports: Map, queuedImports: QueuedImport[]): void { const importName: string = normalizedPath + "/" + declaration.externalIdentifier.name; let resolvedImportName: string = importName; - while (exportsMap.has(resolvedImportName)) - resolvedImportName = (exportsMap.get(resolvedImportName)).importName; - const globalName: string = this.mangleName(declaration); + const seen: Set = new Set(); + while (queuedExports.has(resolvedImportName)) { + const queuedExport: QueuedExport = queuedExports.get(resolvedImportName); + resolvedImportName = queuedExport.importName; + if (seen.has(queuedExport)) + break; + seen.add(queuedExport); + } + const internalName: string = this.mangleInternalName(declaration); if (this.exports.has(resolvedImportName)) { // resolvable right away - if (this.names.has(globalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, globalName); + if (this.names.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else - this.names.set(globalName, this.exports.get(resolvedImportName)); + this.names.set(internalName, this.exports.get(resolvedImportName)); } else { // points to yet unresolved export - const queued: QueuedImport = new QueuedImport(); - queued.globalName = globalName; - queued.importName = importName; - queued.declaration = declaration; - importsQueue.push(queued); + const queuedImport: QueuedImport = new QueuedImport(); + queuedImport.internalName = internalName; + queuedImport.importName = importName; + queuedImport.declaration = declaration; + queuedImports.push(queuedImport); } } private initializeInterface(declaration: InterfaceDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(globalName, declaration); + this.addExport(/* same as */internalName, declaration); const members: Statement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -283,15 +280,15 @@ export class Program extends DiagnosticEmitter { } private initializeMethod(declaration: MethodDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); } private initializeNamespace(declaration: NamespaceDeclaration): void { - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(globalName, declaration); + this.addExport(/* same as */internalName, declaration); const members: Statement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -327,33 +324,33 @@ export class Program extends DiagnosticEmitter { } } - private initializeVariables(statement: VariableStatement, insideNamespace: bool = false): void { - const declarations: VariableDeclaration[] = statement.members; - const isExport: bool = !insideNamespace && hasModifier(ModifierKind.EXPORT, statement.modifiers); + 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) { const declaration: VariableDeclaration = declarations[i]; - const globalName: string = this.mangleName(declaration); - this.addName(globalName, declaration); + const internalName: string = this.mangleInternalName(declaration); + this.addName(internalName, declaration); if (isExport) - this.addExport(globalName, declaration); + this.addExport(/* same as */internalName, declaration); } } - private addName(globalName: string, declaration: DeclarationStatement): void { - if (this.names.has(globalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, globalName); // recoverable + private addName(internalName: string, declaration: DeclarationStatement): void { + if (this.names.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); // recoverable else - this.names.set(globalName, declaration); + this.names.set(internalName, declaration); } - private addExport(globalName: string, declaration: DeclarationStatement): void { - if (this.exports.has(globalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, globalName); // recoverable + private addExport(exportName: string, declaration: DeclarationStatement): void { + if (this.exports.has(exportName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, exportName); // recoverable else - this.exports.set(globalName, declaration); + this.exports.set(exportName, declaration); } - mangleName(declaration: DeclarationStatement): string { + mangleInternalName(declaration: DeclarationStatement): string { let name: string = declaration.identifier.name; let parent: Node | null = declaration.parent; if (parent) { @@ -367,28 +364,28 @@ export class Program extends DiagnosticEmitter { (declaration.kind == NodeKind.FIELD && !hasModifier(ModifierKind.STATIC, (declaration).modifiers)) || (declaration.kind == NodeKind.METHOD && !hasModifier(ModifierKind.STATIC, (declaration).modifiers)) ) - return this.mangleName(parent) + "#" + name; + return this.mangleInternalName(parent) + "#" + name; // otherwise fall through } case NodeKind.ENUM: case NodeKind.ENUMVALUE: case NodeKind.NAMESPACE: - return this.mangleName(parent) + "." + name; + return this.mangleInternalName(parent) + "." + name; case NodeKind.IMPORT: { - const impParent: Node | null = (parent).parent; - if (impParent && impParent.kind == NodeKind.SOURCE) - return (impParent).path + "/" + name; + const importParent: Node | null = (parent).parent; + if (importParent && importParent.kind == NodeKind.SOURCE) + return (importParent).path + "/" + name; break; } case NodeKind.VARIABLE: { - const varParent: Node | null = (parent).parent; - if (varParent) { - if (varParent.kind == NodeKind.SOURCE) - return varParent == this.sources[0] ? name : (varParent).path + "/" + name; - if (varParent.kind == NodeKind.NAMESPACE) - return this.mangleName(varParent) + "." + name; + 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; } @@ -396,34 +393,9 @@ export class Program extends DiagnosticEmitter { } throw new Error("unexpected parent"); } - - addClass(cl: Class): void { - cl.declaration.reflectionIndex = this.classes.length; - this.classes.push(cl); - } - - addEnum(en: Enum): void { - en.declaration.reflectionIndex = this.enums.length; - this.enums.push(en); - } - - addFunction(fn: Function): void { - fn.declaration.reflectionIndex = this.functions.length; - this.functions.push(fn); - } - - addGlobal(gl: GlobalVariable): void { - gl.declaration.reflectionIndex = this.globals.length; - this.globals.push(gl); - } - - addNamespace(ns: Namespace): void { - ns.declaration.reflectionIndex = this.namespaces.length; - this.namespaces.push(ns); - } } -function initializeBasicTypes(types: Map, target: Target) { +function initializeBasicTypes(types: Map, target: Target): void { types.set("i8", Type.i8); types.set("i16", Type.i16); types.set("i32", Type.i32); @@ -435,5 +407,7 @@ function initializeBasicTypes(types: Map, target: Target) { 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/reflection.ts b/src/reflection.ts deleted file mode 100644 index 2bead97a..00000000 --- a/src/reflection.ts +++ /dev/null @@ -1,310 +0,0 @@ -/* - - Reflection objects are largely independent of their respective declarations in - order to make it easier to introduce internal objects. - - Base - ├ Class - ├ Enum - ├ Field - ├ Function - │ └ Method - ├ Import - ├ Namespace - ├ Source - ├ Type ~ TypeKind - └ VariableBase - ├ GlobalVariable - └ LocalVariable - -*/ - -import { - ClassDeclaration, - DeclarationStatement, - EnumDeclaration, - Expression, - FunctionDeclaration, - ImportDeclaration, - ImportStatement, - MethodDeclaration, - ModifierKind, - NamespaceDeclaration, - Node, - FieldDeclaration, - SourceNode, - Statement, - NodeKind, - TypeParameter, - VariableDeclaration, - VariableStatement -} from "./ast"; -import { DiagnosticMessage } from "./diagnostics"; -import { Token, Tokenizer, Range } from "./tokenizer"; -import { hasModifier } from "./parser"; -import { normalizePath, trimExtension } from "./util"; - -export abstract class Base { - - name: string; - exportName: string | null = null; - - constructor(name: string) { - this.name = name; - } - - get isExport(): bool { return this.exportName != null; } - - exportAs(exportName: string): this { - this.exportName = exportName; - return this; - } -} - -export const enum TypeKind { - I8, - I16, - I32, - I64, - ISIZE, - U8, - U16, - U32, - U64, - USIZE, - F32, - F64, - BOOL, - VOID -} - -export function typeKindToString(kind: TypeKind): string { - switch (kind) { - case TypeKind.I8: return "i8"; - case TypeKind.I16: return "i16"; - case TypeKind.I32: return "i32"; - case TypeKind.I64: return "i64"; - case TypeKind.ISIZE: return "isize"; - case TypeKind.U8: return "u8"; - case TypeKind.U16: return "u16"; - case TypeKind.U32: return "u32"; - case TypeKind.U64: return "u64"; - case TypeKind.USIZE: return "usize"; - case TypeKind.F32: return "f32"; - case TypeKind.F64: return "f64"; - case TypeKind.BOOL: return "bool"; - case TypeKind.VOID: return "void"; - } - return "invalid"; -} - -export class Type extends Base { - - kind: TypeKind; - size: i32; - - constructor(kind: TypeKind, size: i32) { - super(typeKindToString(kind)); - this.kind = kind; - this.size = size; - } - - get bitSize(): i32 { return this.size << 3; } - get smallIntegerShift(): i32 { return 32 - (this.size << 3); } - get smallIntegerMask(): i32 { return -1 >>> 32 - (this.size << 3); } - - get isAnyInteger(): bool { return this.kind >= TypeKind.I8 && this.kind <= TypeKind.USIZE; } - get isSmallInteger(): bool { return this.size == 1 || this.size == 2; } - get isLongInteger(): bool { return this.size == 8 && this.kind != TypeKind.F64; } - get isUnsignedInteger(): bool { return this.kind >= TypeKind.U8 && this.kind <= TypeKind.USIZE; } - get isSignedInteger(): bool { return this.kind >= TypeKind.I8 && this.kind <= TypeKind.ISIZE; } - get isAnySize(): bool { return this.kind == TypeKind.ISIZE || this.kind == TypeKind.USIZE; } - get isAnyFloat(): bool { return this.kind == TypeKind.F32 || this.kind == TypeKind.F64; } - - toString(): string { - return typeKindToString(this.kind); - } - - static readonly i8: Type = new Type(TypeKind.I8, 1); - static readonly i16: Type = new Type(TypeKind.I16, 2); - static readonly i32: Type = new Type(TypeKind.I32, 4); - static readonly i64: Type = new Type(TypeKind.I64, 8); - static readonly isize32: Type = new Type(TypeKind.I32, 4); - static readonly isize64: Type = new Type(TypeKind.I64, 8); - static readonly u8: Type = new Type(TypeKind.U8, 1); - static readonly u16: Type = new Type(TypeKind.U16, 2); - static readonly u32: Type = new Type(TypeKind.U32, 4); - static readonly u64: Type = new Type(TypeKind.U64, 8); - static readonly usize32: Type = new Type(TypeKind.U32, 4); - static readonly usize64: Type = new Type(TypeKind.U64, 8); - static readonly f32: Type = new Type(TypeKind.F32, 4); - static readonly f64: Type = new Type(TypeKind.F64, 8); - static readonly bool: Type = new Type(TypeKind.BOOL, 1); - static readonly void: Type = new Type(TypeKind.VOID, 0); -} - -export class Source extends SourceNode { - - text: string; - tokenizer: Tokenizer | null = null; - statements: Statement[]; - isEntry: bool; - normalizedPath: string; - - constructor(path: string, text: string, isEntry: bool = false) { - super(); - this.range = new Range(this, 0, text.length); - this.path = path; - this.text = text; - this.statements = new Array(); - this.isEntry = isEntry; - this.normalizedPath = normalizePath(trimExtension(path)); - } - - get isDeclaration(): bool { return !this.isEntry && this.path.endsWith(".d.ts"); } -} - -export class Import extends Base { - - declaration: ImportDeclaration | null; - externalName: string; - - static create(declaration: ImportStatement): Import[] { - const count: i32 = declaration.declarations.length; - const imports: Import[] = new Array(count); - for (let i: i32 = 0; i < count; ++i) { - const decl: ImportDeclaration = declaration.declarations[i]; - const imprt: Import = new Import(decl.identifier.name); - imprt.declaration = decl; - imprt.externalName = decl.externalIdentifier.name; - imports[i] = imprt; - } - return imports; - } -} - -export abstract class Variable extends Base { - type: Type; -} - -export class GlobalVariable extends Variable { - - declaration: VariableDeclaration; - mutable: bool; - - static create(declaration: VariableStatement): GlobalVariable[] { - const mutable: bool = hasModifier(ModifierKind.CONST, declaration.modifiers); - const count: i32 = declaration.members.length; - const variables: GlobalVariable[] = new Array(count); - for (let i: i32 = 0; i < count; ++i) { - const decl: VariableDeclaration = declaration.members[i]; - const variable: GlobalVariable = new GlobalVariable(decl.identifier.name); - variable.declaration = decl; - variable.mutable = mutable; - variables[i] = variable; - } - return variables; - } -} - -export class LocalVariable extends Variable { - - declaration: VariableDeclaration; - index: i32; - - static create(declaration: VariableStatement, index: i32): LocalVariable[] { - const count: i32 = declaration.members.length; - const variables: LocalVariable[] = new Array(count); - for (let i: i32 = 0; i < count; ++i) { - const decl: VariableDeclaration = declaration.members[i]; - const variable: LocalVariable = new LocalVariable(decl.identifier.name); - variable.declaration = decl; - variable.index = index; - variables[i] = variable; - } - return variables; - } -} - -export class Namespace extends Base { - - declaration: NamespaceDeclaration; - members: Base[]; - - static create(declaration: NamespaceDeclaration): Namespace { - const ns: Namespace = new Namespace(declaration.identifier.name); - ns.declaration = declaration; - const members: Base[] = ns.members = new Array(); - // TODO: insert members - return ns; - } -} - -export class Enum extends Base { - - declaration: EnumDeclaration; - isConst: bool; - values: Map = new Map(); - - static create(declaration: EnumDeclaration): Enum { - const enm: Enum = new Enum(declaration.identifier.name); - enm.declaration = declaration; - enm.isConst = hasModifier(ModifierKind.CONST, declaration.modifiers); - // TODO: insert values - return enm; - } -} - -export class Function extends Base { - - declaration: FunctionDeclaration; - typeArguments: Type[]; - returnType: Type; - statements: Statement[]; - - static create(declaration: FunctionDeclaration, typeArguments: Type[]): Function { - throw new Error("not implemented"); - } -} - -export class Class extends Base { - - declaration: ClassDeclaration; - typeArguments: Type[]; - baseClass: Class | null; - memberNames: Set; - methods: Map; - fields: Map; - - static create(declaration: ClassDeclaration, typeArguments: Type[]): Class { - const clazz: Class = new Class(declaration.identifier.name); - clazz.typeArguments = typeArguments; - return clazz; - } -} - -export class Method extends Function { - - declaration: MethodDeclaration; // more specific - isInstance: bool; - - static create(declaration: MethodDeclaration, typeArguments: Type[]): Method { - throw new Error("not implemented"); - } -} - -export class Field extends Base { - - declaration: FieldDeclaration | null; - type: Type; - offset: i32; - initializer: Expression | null; - - static create(declaration: FieldDeclaration, offset: i32): Field { - const field: Field = new Field(declaration.identifier.name); - field.declaration = declaration; - field.offset = offset; - field.initializer = declaration.initializer; - return field; - } -} diff --git a/src/tokenizer.ts b/src/tokenizer.ts index 92b55422..aade5d26 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -20,7 +20,7 @@ */ import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, formatDiagnosticMessage } from "./diagnostics"; -import { Source } from "./reflection"; +import { Source } from "./ast"; import { I64, CharCode, isLineBreak } from "./util"; export enum Token { diff --git a/src/tsconfig.json b/src/tsconfig.json index 841b22d2..e1ac027c 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -10,6 +10,7 @@ "node" ], "downlevelIteration": true, + "experimentalDecorators": true, "strictNullChecks": true, "alwaysStrict": true, "outDir": "../out" @@ -28,7 +29,10 @@ "program.ts", "reflection.ts", "tokenizer.ts", + "types.ts", "util.ts", - "util/i64.ts" + "util/charcode.ts", + "util/i64.ts", + "util/path.ts" ] } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..e10e5a34 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,138 @@ +export const enum TypeKind { + + // signed integers + I8, + I16, + I32, + I64, + ISIZE, + + // unsigned integers + U8, + U16, + U32, + U64, + USIZE, + BOOL, // sic + + // floats + F32, + F64, + + VOID +} + +export class Type { + + kind: TypeKind; + size: i32; + classType: ClassType | null; + nullable: bool = false; + nullableType: Type | null = null; // cached, of this type + + constructor(kind: TypeKind, size: i32) { + this.kind = kind; + this.size = size; + this.classType = null; + } + + get smallIntegerShift(): i32 { return 32 - this.size; } + get smallIntegerMask(): i32 { return -1 >>> (32 - this.size); } + + get isAnyInteger(): bool { return this.kind >= TypeKind.I8 && this.kind <= TypeKind.BOOL; } + get isSmallInteger(): bool { return this.size != 0 && this.size < 32; } + get isLongInteger(): bool { return this.size == 64 && this.kind != TypeKind.F64; } + get isUnsignedInteger(): bool { return this.kind >= TypeKind.U8 && this.kind <= TypeKind.BOOL; } + get isSignedInteger(): bool { return this.kind >= TypeKind.I8 && this.kind <= TypeKind.ISIZE; } + get isAnySize(): bool { return this.kind == TypeKind.ISIZE || this.kind == TypeKind.USIZE; } + get isAnyFloat(): bool { return this.kind == TypeKind.F32 || this.kind == TypeKind.F64; } + + asClass(classType: ClassType): Type { + const ret: Type = new Type(this.kind, this.size); + ret.classType = classType; + return ret; + } + + asNullable(): Type { + if (!this.nullableType) + (this.nullableType = new Type(this.kind, this.size)).nullable = true; + return this.nullableType; + } + + toString(): string { + switch (this.kind) { + case TypeKind.I8: return "i8"; + case TypeKind.I16: return "i16"; + case TypeKind.I32: return "i32"; + case TypeKind.ISIZE: return "isize"; + case TypeKind.U8: return "u8"; + case TypeKind.U16: return "u16"; + case TypeKind.U32: return "u32"; + case TypeKind.U64: return "u64"; + case TypeKind.USIZE: return "usize"; + case TypeKind.BOOL: return "bool"; + case TypeKind.F32: return "f32"; + case TypeKind.F64: return "f64"; + case TypeKind.VOID: return "void"; + default: + throw new Error("unexpected type kind"); + } + } + + static readonly i8: Type = new Type(TypeKind.I8, 8); + static readonly i16: Type = new Type(TypeKind.I16, 16); + static readonly i32: Type = new Type(TypeKind.I32, 32); + static readonly i64: Type = new Type(TypeKind.I64, 64); + static readonly isize32: Type = new Type(TypeKind.I32, 32); + static readonly isize64: Type = new Type(TypeKind.I64, 64); + static readonly u8: Type = new Type(TypeKind.U8, 8); + static readonly u16: Type = new Type(TypeKind.U16, 16); + static readonly u32: Type = new Type(TypeKind.U32, 32); + static readonly u64: Type = new Type(TypeKind.U64, 64); + static readonly usize32: Type = new Type(TypeKind.U32, 32); + static readonly usize64: Type = new Type(TypeKind.U64, 64); + static readonly bool: Type = new Type(TypeKind.BOOL, 1); + static readonly f32: Type = new Type(TypeKind.F32, 32); + static readonly f64: Type = new Type(TypeKind.F64, 64); + static readonly void: Type = new Type(TypeKind.VOID, 0); +} + +export class FunctionType { + + returnType: Type; + parameterTypes: Type[]; + additionalLocals: Type[]; + typeArgumentsMap: Map | null = null; + + private breakMajor: i32 = 0; + private breakMinor: i32 = 0; + + constructor(returnType: Type, parameterTypes: Type[]) { + this.returnType = returnType; + this.parameterTypes = parameterTypes; + this.additionalLocals = new Array(); + } + + enterBreakContext(): string { + if (!this.breakMinor) + this.breakMajor++; + return this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10); + } + + leaveBreakContext(): void { + if (--this.breakMinor < 0) + throw new Error("unexpected unbalanced break context"); + } +} + +export class ClassType { + + type: Type; + typeArgumentsMap: Map | null = null; + base: ClassType | null; + + constructor(usizeType: Type, base: ClassType | null = null) { + this.type = usizeType.asClass(this); + this.base = base; + } +} diff --git a/src/util.ts b/src/util.ts index 042008ff..cf4b3354 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,235 +1,4 @@ export { I64, U64 } from "./util/i64"; - -export const enum CharCode { - - NULL = 0, - LINEFEED = 0x0A, - CARRIAGERETURN = 0x0D, - LINESEPARATOR = 0x2028, - PARAGRAPHSEPARATOR = 0x2029, - NEXTLINE = 0x0085, - - SPACE = 0x20, - NONBREAKINGSPACE = 0xA0, - ENQUAD = 0x2000, - EMQUAD = 0x2001, - ENSPACE = 0x2002, - EMSPACE = 0x2003, - THREEPEREMSPACE = 0x2004, - FOURPEREMSPACE = 0x2005, - SIXPEREMSPACE = 0x2006, - FIGURESPACE = 0x2007, - PUNCTUATIONSPACE = 0x2008, - THINSPACE = 0x2009, - HAIRSPACE = 0x200A, - ZEROWIDTHSPACE = 0x200B, - NARRINOBREAKSPACE = 0x202F, - IDEOGRAPHICSPACE = 0x3000, - MATHEMATICALSPACE = 0x205F, - OGHAM = 0x1680, - - _ = 0x5F, - $ = 0x24, - - _0 = 0x30, - _1 = 0x31, - _2 = 0x32, - _3 = 0x33, - _4 = 0x34, - _5 = 0x35, - _6 = 0x36, - _7 = 0x37, - _8 = 0x38, - _9 = 0x39, - - a = 0x61, - b = 0x62, - c = 0x63, - d = 0x64, - e = 0x65, - f = 0x66, - g = 0x67, - h = 0x68, - i = 0x69, - j = 0x6A, - k = 0x6B, - l = 0x6C, - m = 0x6D, - n = 0x6E, - o = 0x6F, - p = 0x70, - q = 0x71, - r = 0x72, - s = 0x73, - t = 0x74, - u = 0x75, - v = 0x76, - w = 0x77, - x = 0x78, - y = 0x79, - z = 0x7A, - - A = 0x41, - B = 0x42, - C = 0x43, - D = 0x44, - E = 0x45, - F = 0x46, - G = 0x47, - H = 0x48, - I = 0x49, - J = 0x4A, - K = 0x4B, - L = 0x4C, - M = 0x4D, - N = 0x4E, - O = 0x4F, - P = 0x50, - Q = 0x51, - R = 0x52, - S = 0x53, - T = 0x54, - U = 0x55, - V = 0x56, - W = 0x57, - X = 0x58, - Y = 0x59, - Z = 0x5a, - - AMPERSAND = 0x26, - ASTERISK = 0x2A, - AT = 0x40, - BACKSLASH = 0x5C, - BACKTICK = 0x60, - BAR = 0x7C, - CARET = 0x5E, - CLOSEBRACE = 0x7D, - CLOSEBRACKET = 0x5D, - CLOSEPAREN = 0x29, - COLON = 0x3A, - COMMA = 0x2C, - DOT = 0x2E, - DOUBLEQUOTE = 0x22, - EQUALS = 0x3D, - EXCLAMATION = 0x21, - GREATERTHAN = 0x3E, - HASH = 0x23, - LESSTHAN = 0x3C, - MINUS = 0x2D, - OPENBRACE = 0x7B, - OPENBRACKET = 0x5B, - OPENPAREN = 0x28, - PERCENT = 0x25, - PLUS = 0x2B, - QUESTION = 0x3F, - SEMICOLON = 0x3B, - SINGLEQUOTE = 0x27, - SLASH = 0x2F, - TILDE = 0x7E, - - BACKSPACE = 0x08, - FORMFEED = 0x0C, - BYTEORDERMARK = 0xFEFF, - TAB = 0x09, - VERTICALTAB = 0x0B -} - -export function isLineBreak(c: i32): bool { - return c == CharCode.LINEFEED - || c == CharCode.CARRIAGERETURN - || c == CharCode.LINESEPARATOR - || c == CharCode.PARAGRAPHSEPARATOR; -} - +export { CharCode, isLineBreak} from "./util/charcode"; +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 - -export function normalizePath(path: string, separator: CharCode = CharCode.SLASH): string { - // expects a relative path - - let pos: i32 = 0; - let len: i32 = path.length; - - // trim leading './' - while (pos + 1 < len && path.charCodeAt(pos) == CharCode.DOT && path.charCodeAt(pos + 1) == separator) - pos += 2; - if (pos > 0) { - path = path.substring(pos); - len -= pos; - pos = 0; - } - - let atEnd: bool; - while (pos + 1 < len) { - atEnd = false; - - // we are only interested in '/.' sequences ... - if (path.charCodeAt(pos) == separator && path.charCodeAt(pos + 1) == CharCode.DOT) { - - // '/.' ( '/' | $ ) - if ( - (atEnd = pos + 2 == len) - || - pos + 2 < len && path.charCodeAt(pos + 2) == separator - ) { - path = atEnd - ? path.substring(0, pos) - : path.substring(0, pos) + path.substring(pos + 2); - len -= 2; - continue; - } - - // '/.' ( './' | '.' $ ) - if ( - (atEnd = pos + 3 == len) && path.charCodeAt(pos + 2) == CharCode.DOT - || - pos + 3 < len && path.charCodeAt(pos + 2) == CharCode.DOT && path.charCodeAt(pos + 3) == separator - ) { - - // find preceeding '/' - let ipos: i32 = pos; - while (--ipos >= 0) { - if (path.charCodeAt(ipos) == separator) { - if (pos - ipos != 3 || path.charCodeAt(ipos + 1) != CharCode.DOT || path.charCodeAt(ipos + 2) != CharCode.DOT) { // exclude '..' itself - path = atEnd - ? path.substring(0, ipos) - : path.substring(0, ipos) + path.substring(pos + 3); - len -= pos + 3 - ipos; - pos = ipos - 1; // incremented again at end of loop - } - break; - } - } - - // if there's no preceeding '/', trim start if non-empty - if (ipos < 0 && pos > 0) { - if (pos != 2 || path.charCodeAt(0) != CharCode.DOT || path.charCodeAt(1) != CharCode.DOT) { // exclude '..' itself - path = path.substring(pos + 4); - len = path.length; - continue; - } - } - } - } - pos++; - } - return len > 0 ? path : "."; -} - -export function dirname(normalizedPath: string, separator: CharCode = CharCode.SLASH): string { - let pos: i32 = normalizedPath.length; - while (--pos > 0) - if (normalizedPath.charCodeAt(pos) == separator) - return normalizedPath.substring(0, pos); - return "."; -} - -export function resolvePath(normalizedPath: string, normalizedOrigin: string, separator: CharCode = CharCode.SLASH): string { - return normalizePath(dirname(normalizedOrigin, separator) + String.fromCharCode(separator) + normalizedPath); -} - -export function trimExtension(path: string): string { - const len: i32 = path.length; - if (len > 3 && path.charCodeAt(len - 3) == CharCode.DOT && (path.charCodeAt(len - 2) == CharCode.t || path.charCodeAt(len - 2) == CharCode.a) && path.charCodeAt(len - 1) == CharCode.s) - return path.substring(0, len - 3); - return path; -} diff --git a/src/util/charcode.ts b/src/util/charcode.ts new file mode 100644 index 00000000..23dca035 --- /dev/null +++ b/src/util/charcode.ts @@ -0,0 +1,140 @@ +export const enum CharCode { + + NULL = 0, + LINEFEED = 0x0A, + CARRIAGERETURN = 0x0D, + LINESEPARATOR = 0x2028, + PARAGRAPHSEPARATOR = 0x2029, + NEXTLINE = 0x0085, + + SPACE = 0x20, + NONBREAKINGSPACE = 0xA0, + ENQUAD = 0x2000, + EMQUAD = 0x2001, + ENSPACE = 0x2002, + EMSPACE = 0x2003, + THREEPEREMSPACE = 0x2004, + FOURPEREMSPACE = 0x2005, + SIXPEREMSPACE = 0x2006, + FIGURESPACE = 0x2007, + PUNCTUATIONSPACE = 0x2008, + THINSPACE = 0x2009, + HAIRSPACE = 0x200A, + ZEROWIDTHSPACE = 0x200B, + NARRINOBREAKSPACE = 0x202F, + IDEOGRAPHICSPACE = 0x3000, + MATHEMATICALSPACE = 0x205F, + OGHAM = 0x1680, + + _ = 0x5F, + $ = 0x24, + + _0 = 0x30, + _1 = 0x31, + _2 = 0x32, + _3 = 0x33, + _4 = 0x34, + _5 = 0x35, + _6 = 0x36, + _7 = 0x37, + _8 = 0x38, + _9 = 0x39, + + a = 0x61, + b = 0x62, + c = 0x63, + d = 0x64, + e = 0x65, + f = 0x66, + g = 0x67, + h = 0x68, + i = 0x69, + j = 0x6A, + k = 0x6B, + l = 0x6C, + m = 0x6D, + n = 0x6E, + o = 0x6F, + p = 0x70, + q = 0x71, + r = 0x72, + s = 0x73, + t = 0x74, + u = 0x75, + v = 0x76, + w = 0x77, + x = 0x78, + y = 0x79, + z = 0x7A, + + A = 0x41, + B = 0x42, + C = 0x43, + D = 0x44, + E = 0x45, + F = 0x46, + G = 0x47, + H = 0x48, + I = 0x49, + J = 0x4A, + K = 0x4B, + L = 0x4C, + M = 0x4D, + N = 0x4E, + O = 0x4F, + P = 0x50, + Q = 0x51, + R = 0x52, + S = 0x53, + T = 0x54, + U = 0x55, + V = 0x56, + W = 0x57, + X = 0x58, + Y = 0x59, + Z = 0x5a, + + AMPERSAND = 0x26, + ASTERISK = 0x2A, + AT = 0x40, + BACKSLASH = 0x5C, + BACKTICK = 0x60, + BAR = 0x7C, + CARET = 0x5E, + CLOSEBRACE = 0x7D, + CLOSEBRACKET = 0x5D, + CLOSEPAREN = 0x29, + COLON = 0x3A, + COMMA = 0x2C, + DOT = 0x2E, + DOUBLEQUOTE = 0x22, + EQUALS = 0x3D, + EXCLAMATION = 0x21, + GREATERTHAN = 0x3E, + HASH = 0x23, + LESSTHAN = 0x3C, + MINUS = 0x2D, + OPENBRACE = 0x7B, + OPENBRACKET = 0x5B, + OPENPAREN = 0x28, + PERCENT = 0x25, + PLUS = 0x2B, + QUESTION = 0x3F, + SEMICOLON = 0x3B, + SINGLEQUOTE = 0x27, + SLASH = 0x2F, + TILDE = 0x7E, + + BACKSPACE = 0x08, + FORMFEED = 0x0C, + BYTEORDERMARK = 0xFEFF, + TAB = 0x09, + VERTICALTAB = 0x0B +} + +export function isLineBreak(c: CharCode): bool { + return c == CharCode.LINEFEED + || c == CharCode.CARRIAGERETURN + || c == CharCode.LINESEPARATOR + || c == CharCode.PARAGRAPHSEPARATOR; +} diff --git a/src/util/path.ts b/src/util/path.ts new file mode 100644 index 00000000..a86b15f2 --- /dev/null +++ b/src/util/path.ts @@ -0,0 +1,90 @@ +import { CharCode } from "./charcode"; + +export function normalize(path: string, trimExtension: bool = false, separator: CharCode = CharCode.SLASH): string { + // expects a relative path + + let pos: i32 = 0; + let len: i32 = path.length; + + // trim leading './' + while (pos + 1 < len && path.charCodeAt(pos) == CharCode.DOT && path.charCodeAt(pos + 1) == separator) + pos += 2; + + // trim extension if requested + if (trimExtension && len > pos + 3 && path.charCodeAt(len - 3) == CharCode.DOT && (path.charCodeAt(len - 2) == CharCode.t || path.charCodeAt(len - 2) == CharCode.a) && path.charCodeAt(len - 1) == CharCode.s) + len = len - 3; + + if (pos > 0 || len < path.length) { + path = path.substring(pos, len); + len -= pos; + pos = 0; + } + + let atEnd: bool; + while (pos + 1 < len) { + atEnd = false; + + // we are only interested in '/.' sequences ... + if (path.charCodeAt(pos) == separator && path.charCodeAt(pos + 1) == CharCode.DOT) { + + // '/.' ( '/' | $ ) + if ( + (atEnd = pos + 2 == len) + || + pos + 2 < len && path.charCodeAt(pos + 2) == separator + ) { + path = atEnd + ? path.substring(0, pos) + : path.substring(0, pos) + path.substring(pos + 2); + len -= 2; + continue; + } + + // '/.' ( './' | '.' $ ) + if ( + (atEnd = pos + 3 == len) && path.charCodeAt(pos + 2) == CharCode.DOT + || + pos + 3 < len && path.charCodeAt(pos + 2) == CharCode.DOT && path.charCodeAt(pos + 3) == separator + ) { + + // find preceeding '/' + let ipos: i32 = pos; + while (--ipos >= 0) { + if (path.charCodeAt(ipos) == separator) { + if (pos - ipos != 3 || path.charCodeAt(ipos + 1) != CharCode.DOT || path.charCodeAt(ipos + 2) != CharCode.DOT) { // exclude '..' itself + path = atEnd + ? path.substring(0, ipos) + : path.substring(0, ipos) + path.substring(pos + 3); + len -= pos + 3 - ipos; + pos = ipos - 1; // incremented again at end of loop + } + break; + } + } + + // if there's no preceeding '/', trim start if non-empty + if (ipos < 0 && pos > 0) { + if (pos != 2 || path.charCodeAt(0) != CharCode.DOT || path.charCodeAt(1) != CharCode.DOT) { // exclude '..' itself + path = path.substring(pos + 4); + len = path.length; + continue; + } + } + } + } + pos++; + } + return len > 0 ? path : "."; +} + +export function resolve(normalizedPath: string, normalizedOrigin: string, separator: CharCode = CharCode.SLASH): string { + return normalize(dirname(normalizedOrigin, separator) + String.fromCharCode(separator) + normalizedPath); +} + +export function dirname(normalizedPath: string, separator: CharCode = CharCode.SLASH): string { + let pos: i32 = normalizedPath.length; + while (--pos > 0) + if (normalizedPath.charCodeAt(pos) == separator) + return normalizedPath.substring(0, pos); + return "."; +} diff --git a/tests/compiler.ts b/tests/compiler.ts index bb1ec3f3..cca6e54b 100644 --- a/tests/compiler.ts +++ b/tests/compiler.ts @@ -3,10 +3,10 @@ import { Compiler } from "../src/compiler"; import { Parser } from "../src/parser"; const files: Map = new Map([ - ["main", `import { Test } from "./a"; export { TestAlias } from "./d";`], + ["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 }`], + ["c", `export enum Test { ONE = 1, TWO = 2 }`], ["d", `export { Test as TestAlias } from "./b";`] ]); @@ -24,14 +24,14 @@ const program = parser.finish(); const compiler = new Compiler(program); const module = compiler.compile(); -console.log("names", program.names); -console.log("exports", program.exports); +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 -/* _BinaryenModulePrint(module.ref); +_BinaryenModulePrint(module.ref); -console.log("--- statements ---"); +/* console.log("--- statements ---"); compiler.statements.forEach(stmt => { _BinaryenExpressionPrint(stmt); }); */ \ No newline at end of file diff --git a/tests/parser/fixtures/function.ts b/tests/parser/fixtures/function.ts index 3ac855de..64221f7e 100644 --- a/tests/parser/fixtures/function.ts +++ b/tests/parser/fixtures/function.ts @@ -2,3 +2,6 @@ function simple(): void { } function typeparams(a: V | null = null): void { } +@decorator() +function withdecorator(): void { +}