From 1d53303b47789d49c7be6ab6418273662c5cb8b9 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Thu, 28 Sep 2017 13:08:25 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + README.md | 25 + package-lock.json | 219 +++ package.json | 16 + scripts/build-diagnostics.js | 32 + src/ast.ts | 1785 ++++++++++++++++++++++ src/binaryen.d.ts | 252 +++ src/binaryen.ts | 585 +++++++ src/compiler.ts | 653 ++++++++ src/diagnosticMessages.generated.ts | 100 ++ src/diagnosticMessages.json | 49 + src/diagnostics.ts | 167 ++ src/glue/js.d.ts | 16 + src/glue/js.ts | 16 + src/glue/wasm.ts | 0 src/index.ts | 58 + src/parser.ts | 1543 +++++++++++++++++++ src/program.ts | 325 ++++ src/reflection.ts | 310 ++++ src/tokenizer.ts | 1158 ++++++++++++++ src/tsconfig.json | 33 + src/util.ts | 228 +++ src/util/i64.ts | 520 +++++++ tests/binaryen.ts | 56 + tests/i64.ts | 37 + tests/parser/fixtures/class.ts | 12 + tests/parser/fixtures/enum.ts | 10 + tests/parser/fixtures/function.ts | 4 + tests/parser/fixtures/precedence.tree.ts | 171 +++ tests/parser/index.ts | 46 + tests/path.ts | 8 + tests/tokenizer.ts | 23 + 32 files changed, 8460 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/build-diagnostics.js create mode 100644 src/ast.ts create mode 100644 src/binaryen.d.ts create mode 100644 src/binaryen.ts create mode 100644 src/compiler.ts create mode 100644 src/diagnosticMessages.generated.ts create mode 100644 src/diagnosticMessages.json create mode 100644 src/diagnostics.ts create mode 100644 src/glue/js.d.ts create mode 100644 src/glue/js.ts create mode 100644 src/glue/wasm.ts create mode 100644 src/index.ts create mode 100644 src/parser.ts create mode 100644 src/program.ts create mode 100644 src/reflection.ts create mode 100644 src/tokenizer.ts create mode 100644 src/tsconfig.json create mode 100644 src/util.ts create mode 100644 src/util/i64.ts create mode 100644 tests/binaryen.ts create mode 100644 tests/i64.ts create mode 100644 tests/parser/fixtures/class.ts create mode 100644 tests/parser/fixtures/enum.ts create mode 100644 tests/parser/fixtures/function.ts create mode 100644 tests/parser/fixtures/precedence.tree.ts create mode 100644 tests/parser/index.ts create mode 100644 tests/path.ts create mode 100644 tests/tokenizer.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6426b958 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +npm-debug.* +out/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..f04dd021 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +AssemblyScript NEXT +=================== + +This repository contains compiler component prototypes for the next iteration of the AssemblyScript compiler written in AssemblyScript itself. + +Note that the code uses some features and standard library components that are not yet supported by any version of asc. To account for this, the code has been written in "portable AssemblyScript", a TypeScript-compatible subset of a subset of a superset of JavaScript, that also compiles to JavaScript using TSC. + +Why is this necessary? +---------------------- + +Well, it isn't, but: In order to be able to compile the AssemblyScript compiler itself to WebAssembly eventually, we cannot depend on TypeScript because it is written in vanilla TypeScript and makes use of quite a few non-AOT-compatible dynamic features of JavaScript. + +Cons: +- A lot of work +- Dealing with TypeScript compatibility issues + +Pros: +- One day compiling to WebAssembly for performance +- Necessary features only, reducing binary size +- Linking against Binaryen compiled to WebAssembly, reducing overhead + +Side effects: +- Good fire test for the compiler +- Good benchmark when comparing both versions +- Benefits standard library design ideas diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..b4be965b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,219 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@types/chalk": { + "version": "0.4.31", + "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz", + "integrity": "sha1-ox10JBprHtu5c8822XooloNKUfk=", + "dev": true + }, + "@types/diff": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-3.2.2.tgz", + "integrity": "sha512-q3zfJvaTroV5BjAAR+peTHEGAAhGrPX0z2EzCzpt2mwFA+qzUn2nigJLqSekXRtdULKmT8am7zjvTMZSapIgHw==", + "dev": true + }, + "@types/long": { + "version": "3.0.32", + "resolved": "https://registry.npmjs.org/@types/long/-/long-3.0.32.tgz", + "integrity": "sha512-ZXyOOm83p7X8p3s0IYM3VeueNmHpkk/yMlP8CLeOnEcu6hIwPH7YjZBvhQkR0ZFS2DqZAxKtJ/M5fcuv3OU5BA==", + "dev": true + }, + "@types/node": { + "version": "8.0.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz", + "integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ==" + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "binaryen": { + "version": "37.0.0-nightly.20170912", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-37.0.0-nightly.20170912.tgz", + "integrity": "sha512-yXLgHkUvTMqEV1vkixAaldnS4w6WOrqY+30Cx9k+JjBzmA6wJTQr0F32xFg/4MPr4NRZOdP/AnI8ais4nhfgIw==" + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.4.0" + } + }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true + }, + "make-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz", + "integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "ts-node": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz", + "integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=", + "dev": true, + "requires": { + "arrify": "1.0.1", + "chalk": "2.1.0", + "diff": "3.3.1", + "make-error": "1.3.0", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18", + "tsconfig": "6.0.0", + "v8flags": "3.0.0", + "yn": "2.0.0" + } + }, + "tsconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", + "integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=", + "dev": true, + "requires": { + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1" + } + }, + "typescript": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", + "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=", + "dev": true + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "v8flags": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.0.tgz", + "integrity": "sha512-AGl+C+4qpeSu2g3JxCD/mGFFOs/vVZ3XREkD3ibQXEqr4Y4zgIrPWW124/IKJFHOIVFIoH8miWrLf0o84HYjwA==", + "dev": true, + "requires": { + "user-home": "1.1.1" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..693ecc51 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "devDependencies": { + "@types/chalk": "^0.4.31", + "@types/diff": "^3.2.2", + "@types/long": "^3.0.32", + "chalk": "^2.1.0", + "diff": "^3.3.1", + "long": "^3.2.0", + "ts-node": "^3.3.0", + "typescript": "^2.5.2" + }, + "dependencies": { + "@types/node": "^8.0.28", + "binaryen": "37.0.0-nightly.20170912" + } +} diff --git a/scripts/build-diagnostics.js b/scripts/build-diagnostics.js new file mode 100644 index 00000000..c1a9adcd --- /dev/null +++ b/scripts/build-diagnostics.js @@ -0,0 +1,32 @@ +var fs = require("fs"); + +var messages = require(__dirname + "/../src/diagnosticMessages.json"); + +var sb = [ "// code below is generated from diagnosticsMessages.json by scripts/build-diagnostics\n\n" ]; + +function makeKey(text) { + return text.replace(/[^\w]+/g, "_").replace(/_+$/, ""); +} + +sb.push("export enum DiagnosticCode {\n"); + +var first = true; +Object.keys(messages).forEach(text => { + var key = makeKey(text); + if (first) + first = false; + else { + sb.push(",\n"); + } + sb.push(" " + key + " = " + messages[text]); +}); + +sb.push("\n}\n\nexport function diagnosticCodeToString(code: DiagnosticCode): string {\n switch (code) {\n"); + +Object.keys(messages).forEach(text => { + sb.push(" case " + messages[text] + ": return " + JSON.stringify(text) + ";\n"); +}); + +sb.push(" default: return \"\";\n }\n}\n"); + +fs.writeFileSync(__dirname + "/../src/diagnosticMessages.generated.ts", sb.join(""), { encoding: "utf8" }); diff --git a/src/ast.ts b/src/ast.ts new file mode 100644 index 00000000..82aad4ba --- /dev/null +++ b/src/ast.ts @@ -0,0 +1,1785 @@ +/* + + Similar to TypeScript's syntax tree of node interfaces, but class-based. + + Node ~ NodeKind + ├ ExportMember + ├ Expression + │ ├ LiteralExpression ~ LiteralKind + │ │ ├ ArrayLiteralExpression + │ │ ├ FloatLiteralExpression + │ │ ├ IntegerLiteralExpression + │ │ ├ RegexpLiteralExpression + │ │ └ StringLiteralExpression + │ ├ AssertionExpression + │ ├ BinaryExpression + │ ├ CallExpression + │ ├ ElementAccessExpression + │ ├ IdentifierExpression + │ ├ ParenthesizedExpression + │ ├ NewExpression + │ ├ PropertyAccessExpression + │ ├ SelectExpression + │ └ UnaryExpression + │ ├ UnaryPostfixExpression + │ └ UnaryPrefixExpression + ├ Statement + │ ├ BlockStatement + │ ├ BreakStatement + │ ├ ContinueStatement + │ ├ DeclarationStatement + │ │ ├ ClassDeclaration <> TypeParameter + │ │ ├ EnumDeclaration <> EnumValueDeclaration + │ │ ├ EnumValueDeclaration + │ │ ├ FieldDeclaration + │ │ ├ FunctionDeclaration <> TypeParameter, Parameter + │ │ │ └ MethodDeclaration + │ │ ├ ImportDeclaration + │ │ ├ InterfaceDeclaration + │ │ ├ NamespaceDeclaration + │ │ └ VariableDeclaration + │ ├ DoStatement + │ ├ EmptyStatement + │ ├ ExportImportStatement + │ ├ ExportStatement <> ExportMember + │ ├ ExpressionStatement + │ ├ ForStatement + │ ├ IfStatement + │ ├ ImportStatement <> ImportDeclaration + │ ├ ReturnStatement + │ ├ SwitchStatement <> SwitchCase + │ ├ ThrowStatement + │ ├ TryStatement + │ ├ VariableStatement <> VariableDeclaration + │ └ WhileStatement + ├ Parameter + ├ SwitchCase + ├ TypeNode + └ TypeParameter + + All nodes are backwards-serializable to their respective source except that + formatting is lost, long integers become hex literals and semicolons will be + inserted. Useful for testing. + + serialize(node) + +*/ + +import { Token, operatorTokenToString, Range } from "./tokenizer"; +import { CharCode, I64, sb } from "./util"; + +export { Range } from "./tokenizer"; + +export abstract class Node { + + kind: NodeKind; + range: Range; + parent: Node | null = null; + + abstract serialize(sb: string[]): void; +} + +export enum NodeKind { + + SOURCE, + + // types + TYPE, + TYPEPARAMETER, + + // expressions + IDENTIFIER, + ASSERTION, + BINARY, + CALL, + ELEMENTACCESS, + LITERAL, + NEW, + PARENTHESIZED, + PROPERTYACCESS, + SELECT, + UNARYPOSTFIX, + UNARYPREFIX, + + // statements + BLOCK, + BREAK, + CLASS, // is also declaration + CONTINUE, + DO, + EMPTY, + ENUM, // is also declaration + ENUMVALUE, // is also declaration + EXPORT, + EXPORTIMPORT, + EXPRESSION, + INTERFACE, + FOR, + FUNCTION, // is also declaration + IF, + IMPORT, // wraps declarations + IMPORTDECLARATION, + METHOD, // is also declaration + NAMESPACE, // is also declaration + FIELD, + RETURN, + SWITCH, + THROW, + TRY, + VARIABLE, // wraps declarations + VARIABLEDECLARATION, + WHILE +} + +export function nodeKindToString(kind: NodeKind): string { + switch (kind) { + case NodeKind.SOURCE: return "SOURCE"; + case NodeKind.TYPE: return "TYPE"; + case NodeKind.TYPEPARAMETER: return "TYPEPARAMETER"; + case NodeKind.IDENTIFIER: return "IDENTIFIER"; + case NodeKind.ASSERTION: return "ASSERTION"; + case NodeKind.BINARY: return "BINARY"; + case NodeKind.CALL: return "CALL"; + case NodeKind.ELEMENTACCESS: return "ELEMENTACCESS"; + case NodeKind.LITERAL: return "LITERAL"; + case NodeKind.NEW: return "NEW"; + case NodeKind.PARENTHESIZED: return "PARENTHESIZED"; + case NodeKind.PROPERTYACCESS: return "PROPERTYACCESS"; + case NodeKind.SELECT: return "SELECT"; + case NodeKind.UNARYPOSTFIX: return "UNARYPOSTFIX"; + case NodeKind.UNARYPREFIX: return "UNARYPREFIX"; + case NodeKind.BLOCK: return "BLOCK"; + case NodeKind.BREAK: return "BREAK"; + case NodeKind.CLASS: return "CLASS"; + case NodeKind.CONTINUE: return "CONTINUE"; + case NodeKind.DO: return "DO"; + case NodeKind.EMPTY: return "EMPTY"; + case NodeKind.ENUM: return "ENUM"; + case NodeKind.ENUMVALUE: return "ENUMVALUE"; + case NodeKind.EXPORT: return "EXPORT"; + case NodeKind.EXPORTIMPORT: return "EXPORTIMPORT"; + case NodeKind.EXPRESSION: return "EXPRESSION"; + case NodeKind.INTERFACE: return "INTERFACE"; + case NodeKind.FOR: return "FOR"; + case NodeKind.FUNCTION: return "FUNCTION"; + case NodeKind.IF: return "IF"; + case NodeKind.IMPORT: return "IMPORT"; + case NodeKind.IMPORTDECLARATION: return "IMPORTDECLARATION"; + case NodeKind.METHOD: return "METHOD"; + case NodeKind.NAMESPACE: return "NAMESPACE"; + case NodeKind.FIELD: return "PROPERTY"; + case NodeKind.RETURN: return "RETURN"; + case NodeKind.SWITCH: return "SWITCH"; + case NodeKind.THROW: return "THROW"; + case NodeKind.TRY: return "TRY"; + case NodeKind.VARIABLE: return "VARIABLE"; + case NodeKind.VARIABLEDECLARATION: return "VARIABLEDECLARATION"; + case NodeKind.WHILE: return "WHILE"; + default: return ""; + } +} + +// types + +export class TypeNode extends Node { + + kind = NodeKind.TYPE; + identifier: IdentifierExpression; + parameters: TypeNode[]; + nullable: bool; + + static create(identifier: IdentifierExpression, parameters: TypeNode[], nullable: bool, range: Range): TypeNode { + const type: TypeNode = new TypeNode(); + type.range = range; + type.identifier = identifier; + type.parameters = parameters; + type.nullable = nullable; + return type; + } + + serialize(sb: string[]): void { + this.identifier.serialize(sb); + if (this.parameters.length) { + sb.push("<"); + for (let i: i32 = 0; i < this.parameters.length; ++i) { + if (i > 0) + sb.push(", "); + this.parameters[i].serialize(sb); + } + sb.push(">"); + } + if (this.nullable) + sb.push(" | null"); + } +} + +export class TypeParameter extends Node { + + kind = NodeKind.TYPEPARAMETER; + identifier: IdentifierExpression; + extendsType: TypeNode | null; + + serialize(sb: string[]): void { + this.identifier.serialize(sb); + if (this.extendsType) { + sb.push(" extends "); + (this.extendsType).serialize(sb); + } + } +} + +// expressions + +export abstract class Expression extends Node { + + static createIdentifier(name: string, range: Range): IdentifierExpression { + const expr: IdentifierExpression = new IdentifierExpression(); + expr.range = range; + expr.name = name; + return expr; + } + + static createArrayLiteral(elementExpressions: (Expression | null)[], range: Range): ArrayLiteralExpression { + const expr: ArrayLiteralExpression = new ArrayLiteralExpression(); + expr.range = range; + for (let i: i32 = 0, k: i32 = (expr.elementExpressions = elementExpressions).length; i < k; ++i) + if (elementExpressions[i]) (elementExpressions[i]).parent = expr; + return expr; + } + + static createAssertion(assertionKind: AssertionKind, expression: Expression, toType: TypeNode, range: Range): AssertionExpression { + const expr: AssertionExpression = new AssertionExpression(); + expr.range = range; + expr.assertionKind = assertionKind; + (expr.expression = expression).parent = expr; + (expr.toType = toType).parent = expr; + return expr; + } + + static createBinary(operator: Token, left: Expression, right: Expression, range: Range): BinaryExpression { + const expr: BinaryExpression = new BinaryExpression(); + expr.range = range; + expr.operator = operator; + (expr.left = left).parent = expr; + (expr.right = right).parent = expr; + return expr; + } + + static createCall(expression: Expression, typeArguments: TypeNode[], args: Expression[], range: Range): CallExpression { + const expr: CallExpression = new CallExpression(); + expr.range = range; + (expr.expression = expression).parent = expr; + let i: i32, k: i32; + for (i = 0, k = (expr.typeArguments = typeArguments).length; i < k; ++i) typeArguments[i].parent = expr; + for (i = 0, k = (expr.arguments = args).length; i < k; ++i) args[i].parent = expr; + return expr; + } + + static createElementAccess(expression: Expression, element: Expression, range: Range): ElementAccessExpression { + const expr: ElementAccessExpression = new ElementAccessExpression(); + expr.range = range; + (expr.expression = expression).parent = expr; + (expr.elementExpression = element).parent = expr; + return expr; + } + + static createFloatLiteral(value: f64, range: Range): FloatLiteralExpression { + const expr: FloatLiteralExpression = new FloatLiteralExpression(); + expr.range = range; + expr.value = value; + return expr; + } + + static createIntegerLiteral(value: I64, range: Range): IntegerLiteralExpression { + const expr: IntegerLiteralExpression = new IntegerLiteralExpression(); + expr.range = range; + expr.value = value; + return expr; + } + + static createNew(expression: Expression, typeArguments: TypeNode[], args: Expression[], range: Range): NewExpression { + const expr: NewExpression = new NewExpression(); + expr.range = range; + (expr.expression = expression).parent = expr; + let i: i32, k: i32; + for (i = 0, k = (expr.typeArguments = typeArguments).length; i < k; ++i) typeArguments[i].parent = expr; + for (i = 0, k = (expr.arguments = args).length; i < k; ++i) args[i].parent = expr; + return expr; + } + + static createParenthesized(expression: Expression, range: Range): ParenthesizedExpression { + const expr: ParenthesizedExpression = new ParenthesizedExpression(); + expr.range = range; + (expr.expression = expression).parent = expr; + return expr; + } + + static createPropertyAccess(expression: Expression, property: IdentifierExpression, range: Range): PropertyAccessExpression { + const expr: PropertyAccessExpression = new PropertyAccessExpression(); + expr.range = range; + (expr.expression = expression).parent = expr; + (expr.property = property).parent = expr; + return expr; + } + + static createRegexpLiteral(value: string, range: Range): RegexpLiteralExpression { + const expr: RegexpLiteralExpression = new RegexpLiteralExpression(); + expr.range = range; + expr.value = value; + return expr; + } + + static createSelect(condition: Expression, ifThen: Expression, ifElse: Expression, range: Range): SelectExpression { + const expr: SelectExpression = new SelectExpression(); + expr.range = range; + (expr.condition = condition).parent = expr; + (expr.ifThen = ifThen).parent = expr; + (expr.ifElse = ifElse).parent = expr; + return expr; + } + + static createStringLiteral(value: string, range: Range): StringLiteralExpression { + const expr: StringLiteralExpression = new StringLiteralExpression(); + expr.range = range; + expr.value = value; + return expr; + } + + static createUnaryPostfix(operator: Token, expression: Expression, range: Range): UnaryPostfixExpression { + const expr: UnaryPostfixExpression = new UnaryPostfixExpression(); + expr.range = range; + expr.operator = operator; + (expr.expression = expression).parent = expr; + return expr; + } + + static createUnaryPrefix(operator: Token, expression: Expression, range: Range): UnaryPrefixExpression { + const expr: UnaryPrefixExpression = new UnaryPrefixExpression(); + expr.range = range; + expr.operator = operator; + (expr.expression = expression).parent = expr; + return expr; + } + + abstract serializeAsTree(sb: string[], indent: i32); +} + +export const enum LiteralKind { + FLOAT, + INTEGER, + STRING, + REGEXP, + ARRAY, + OBJECT +} + +export function literalKindToString(kind: LiteralKind): string { + switch (kind) { + case LiteralKind.FLOAT: return "FLOAT"; + case LiteralKind.INTEGER: return "INTEGER"; + case LiteralKind.STRING: return "STRING"; + case LiteralKind.REGEXP: return "REGEXP"; + case LiteralKind.ARRAY: return "ARRAY"; + case LiteralKind.OBJECT: return "OBJECT"; + default: return ""; + } +} + +export abstract class LiteralExpression extends Expression { + kind = NodeKind.LITERAL; + literalKind: LiteralKind; +} + +export class ArrayLiteralExpression extends LiteralExpression { + + literalKind = LiteralKind.ARRAY; + elementExpressions: (Expression | null)[]; + + serialize(sb: string[]): void { + sb.push("["); + for (let i: i32 = 0; i < this.elementExpressions.length; ++i) { + if (i > 0) + sb.push(", "); + if (this.elementExpressions[i]) + (this.elementExpressions[i]).serialize(sb); + } + sb.push("]"); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent++); + sb.push("[\n"); + for (let i: i32 = 0; i < this.elementExpressions.length; ++i) { + pushIndent(sb, indent); + if (i > 0) + sb.push(",\n"); + if (this.elementExpressions[i]) + (this.elementExpressions[i]).serialize(sb); + } + pushIndent(sb, --indent); + sb.push("]"); + } +} + +export const enum AssertionKind { + PREFIX, + AS +} + +export class AssertionExpression extends Expression { + + kind = NodeKind.ASSERTION; + assertionKind: AssertionKind; + expression: Expression; + toType: TypeNode; + + serialize(sb: string[]): void { + if (this.assertionKind == AssertionKind.PREFIX) { + sb.push("<"); + this.toType.serialize(sb); + sb.push(">"); + this.expression.serialize(sb); + } else { + this.expression.serialize(sb); + sb.push(" as "); + this.toType.serialize(sb); + } + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + if (this.assertionKind == AssertionKind.PREFIX) { + pushIndent(sb, indent); + sb.push("<"); + this.toType.serialize(sb); + sb.push(">\n"); + this.expression.serializeAsTree(sb, indent + 1); + } else { + this.expression.serializeAsTree(sb, indent + 1); + sb.push("\n"); + pushIndent(sb, indent); + sb.push("as\n"); + pushIndent(sb, indent + 1); + this.toType.serialize(sb); + } + sb.push("\n"); + } +} + +export class BinaryExpression extends Expression { + + kind = NodeKind.BINARY; + operator: Token; + left: Expression; + right: Expression; + + serialize(sb: string[]): void { + this.left.serialize(sb); + sb.push(" "); + sb.push(operatorTokenToString(this.operator)); + sb.push(" "); + this.right.serialize(sb); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + this.left.serializeAsTree(sb, indent + 1); + pushIndent(sb, indent); + sb.push(operatorTokenToString(this.operator)); + sb.push("\n"); + this.right.serializeAsTree(sb, indent + 1); + } +} + +export class CallExpression extends Expression { + + kind = NodeKind.CALL; + expression: Expression; + typeArguments: TypeNode[]; + arguments: Expression[]; + + serialize(sb: string[]): void { + this.expression.serialize(sb); + if (this.typeArguments.length) { + sb.push("<"); + for (let i: i32 = 0; i < this.typeArguments.length; ++i) { + if (i > 0) + sb.push(", "); + this.typeArguments[i].serialize(sb); + } + sb.push(">("); + } else + sb.push("("); + for (let i: i32 = 0; i < this.arguments.length; ++i) { + if (i > 0) + sb.push(", "); + this.arguments[i].serialize(sb); + } + sb.push(")"); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); // not interested in a tree here + sb.push("\n"); + } +} + +export class ElementAccessExpression extends Expression { + + kind = NodeKind.ELEMENTACCESS; + expression: Expression; + elementExpression: Expression; + + serialize(sb: string[]): void { + this.expression.serialize(sb); + sb.push("["); + this.elementExpression.serialize(sb); + sb.push("]"); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + this.expression.serializeAsTree(sb, indent); + pushIndent(sb, indent); + sb.push("[\n"); + this.elementExpression.serializeAsTree(sb, indent + 1); + pushIndent(sb, indent); + sb.push("]\n"); + } +} + +export class FloatLiteralExpression extends LiteralExpression { + + literalKind = LiteralKind.FLOAT; + value: f64; + + serialize(sb: string[]): void { + sb.push(this.value.toString()); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); + sb.push("\n"); + } +} + +export class IdentifierExpression extends Expression { + + kind = NodeKind.IDENTIFIER; + name: string; + + serialize(sb: string[]): void { + sb.push(this.name); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); + sb.push("\n"); + } +} + +export class IntegerLiteralExpression extends LiteralExpression { + + literalKind = LiteralKind.INTEGER; + value: I64; + + serialize(sb: string[]): void { + sb.push(this.value.toString()); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); + sb.push("\n"); + } +} + +export class NewExpression extends CallExpression { + + kind = NodeKind.NEW; + + serialize(sb: string[]): void { + sb.push("new "); + super.serialize(sb); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); // not interested in a tree here + sb.push("\n"); + } +} + +export class ParenthesizedExpression extends Expression { + + kind = NodeKind.PARENTHESIZED; + expression: Expression; + + serialize(sb: string[]): void { + sb.push("("); + this.expression.serialize(sb); + sb.push(")"); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + sb.push("(\n"); + this.expression.serializeAsTree(sb, indent + 1); + pushIndent(sb, indent); + sb.push(")\n"); + } +} + +export class PropertyAccessExpression extends Expression { + + kind = NodeKind.PROPERTYACCESS; + expression: Expression; + property: IdentifierExpression; + + serialize(sb: string[]): void { + this.expression.serialize(sb); + sb.push("."); + this.property.serialize(sb); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); // not interested in a tree here + sb.push("\n"); + } +} + +export class RegexpLiteralExpression extends LiteralExpression { + + literalKind = LiteralKind.REGEXP; + value: string; + + serialize(sb: string[]): void { + sb.push(this.value); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); + sb.push("\n"); + } +} + +export class SelectExpression extends Expression { + + kind = NodeKind.SELECT; + condition: Expression; + ifThen: Expression; + ifElse: Expression; + + serialize(sb: string[]): void { + this.condition.serialize(sb); + sb.push(" ? "); + this.ifThen.serialize(sb); + sb.push(" : "); + this.ifElse.serialize(sb); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + this.condition.serializeAsTree(sb, indent + 1); + pushIndent(sb, indent); + sb.push("?\n"); + this.ifThen.serializeAsTree(sb, indent + 1); + pushIndent(sb, indent); + sb.push(":\n"); + this.ifElse.serializeAsTree(sb, indent + 1); + } +} + +export class StringLiteralExpression extends LiteralExpression { + + literalKind = LiteralKind.STRING; + value: string; + + serialize(sb: string[]): void { + sb.push(JSON.stringify(this.value)); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + this.serialize(sb); + sb.push("\n"); + } +} + +export abstract class UnaryExpression extends Expression { + operator: Token; + expression: Expression; +} + +export class UnaryPostfixExpression extends UnaryExpression { + + kind = NodeKind.UNARYPOSTFIX; + + serialize(sb: string[]): void { + this.expression.serialize(sb); + switch (this.operator) { + case Token.PLUS_PLUS: sb.push("++"); break; + case Token.MINUS_MINUS: sb.push("--"); break; + default: sb.push("INVALID"); break; + } + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + this.expression.serializeAsTree(sb, indent + 1); + pushIndent(sb, indent); + switch (this.operator) { + case Token.PLUS_PLUS: sb.push("++"); break; + case Token.MINUS_MINUS: sb.push("--"); break; + default: sb.push("INVALID"); break; + } + sb.push("\n"); + } +} + +export class UnaryPrefixExpression extends UnaryExpression { + + kind = NodeKind.UNARYPREFIX; + + serialize(sb: string[]): void { + sb.push(operatorTokenToString(this.operator)); + this.expression.serialize(sb); + } + + serializeAsTree(sb: string[], indent: i32 = 0): void { + pushIndent(sb, indent); + sb.push(operatorTokenToString(this.operator)); + sb.push("\n"); + this.expression.serializeAsTree(sb, indent + 1); + } +} + +// statements + +export enum ModifierKind { + ASYNC, + CONST, + DECLARE, + EXPORT, + IMPORT, + STATIC, + ABSTRACT, + PUBLIC, + PRIVATE, + PROTECTED, + GET, + SET +} + +export function modifierKindToString(kind: ModifierKind): string { + switch (kind) { + case ModifierKind.ASYNC: return "ASYNC"; + case ModifierKind.CONST: return "CONST"; + case ModifierKind.DECLARE: return "DECLARE"; + case ModifierKind.EXPORT: return "EXPORT"; + case ModifierKind.IMPORT: return "IMPORT"; + case ModifierKind.STATIC: return "STATIC"; + case ModifierKind.ABSTRACT: return "ABSTRACT"; + case ModifierKind.PUBLIC: return "PUBLIC"; + case ModifierKind.PRIVATE: return "PRIVATE"; + case ModifierKind.PROTECTED: return "PROTECTED"; + case ModifierKind.GET: return "GET"; + case ModifierKind.SET: return "SET"; + default: return ""; + } +} + +export abstract class Statement extends Node { + + static createBlock(statements: Statement[], range: Range): BlockStatement { + const stmt: BlockStatement = new BlockStatement(); + stmt.range = range; + for (let i: i32 = 0, k = (stmt.statements = statements).length; i < k; ++i) statements[i].parent = stmt; + return stmt; + } + + static createBreak(label: IdentifierExpression | null, range: Range): BreakStatement { + const stmt: BreakStatement = new BreakStatement(); + stmt.range = range; + if (stmt.label = label) (label).parent = stmt; + return stmt; + } + + static createClass(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], extendsType: TypeNode | null, implementsTypes: TypeNode[], members: DeclarationStatement[], range: Range): ClassDeclaration { + const stmt: ClassDeclaration = new ClassDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + (stmt.identifier = identifier).parent = stmt; + for (i = 0, k = (stmt.typeParameters = typeParameters).length; i < k; ++i) typeParameters[i].parent = stmt; + 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; + return stmt; + } + + static createContinue(label: IdentifierExpression | null, range: Range): ContinueStatement { + const stmt: ContinueStatement = new ContinueStatement(); + stmt.range = range; + if (stmt.label = label) (label).parent = stmt; + return stmt; + } + + static createDo(statement: Statement, condition: Expression, range: Range): DoStatement { + const stmt: DoStatement = new DoStatement(); + stmt.range = range; + (stmt.statement = statement).parent = stmt; + (stmt.condition = condition).parent = stmt; + return stmt; + } + + static createEmpty(range: Range): EmptyStatement { + const stmt: EmptyStatement = new EmptyStatement(); + stmt.range = range; + return stmt; + } + + static createEnum(modifiers: Modifier[], identifier: IdentifierExpression, members: EnumValueDeclaration[], range: Range): EnumDeclaration { + const stmt: EnumDeclaration = new EnumDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + (stmt.identifier = identifier).parent = stmt; + for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; + return stmt; + } + + static createEnumValue(identifier: IdentifierExpression, value: Expression | null, range: Range): EnumValueDeclaration { + const stmt: EnumValueDeclaration = new EnumValueDeclaration(); + stmt.range = range; + (stmt.identifier = identifier).parent = stmt; + if (stmt.value = value) (value).parent = stmt; + return stmt; + } + + static createExport(modifiers: Modifier[], members: ExportMember[], path: string | null, range: Range): ExportStatement { + const stmt: ExportStatement = new ExportStatement(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; + stmt.path = path; + return stmt; + } + + static createExportImport(identifier: IdentifierExpression, asIdentifier: IdentifierExpression, range: Range): ExportImportStatement { + const stmt: ExportImportStatement = new ExportImportStatement(); + stmt.range = range; + (stmt.identifier = identifier).parent = stmt; + (stmt.asIdentifier = asIdentifier).parent = stmt; + return stmt; + } + + static createExportMember(identifier: IdentifierExpression, asIdentifier: IdentifierExpression | null, range: Range): ExportMember { + const elem: ExportMember = new ExportMember(); + elem.range = range; + (elem.identifier = identifier).parent = elem; + if (elem.asIdentifier = asIdentifier) (asIdentifier).parent = elem; + return elem; + } + + static createExpression(expression: Expression): ExpressionStatement { + const stmt: ExpressionStatement = new ExpressionStatement(); + stmt.range = expression.range; + (stmt.expression = expression).parent = stmt; + return stmt; + } + + static createIf(condition: Expression, statement: Statement, elseStatement: Statement | null, range: Range): IfStatement { + const stmt: IfStatement = new IfStatement(); + stmt.range = range; + (stmt.condition = condition).parent = stmt; + (stmt.statement = statement).parent = stmt; + if (stmt.elseStatement = elseStatement) (elseStatement).parent = stmt; + return stmt; + } + + static createImport(declarations: ImportDeclaration[], path: string, range: Range): ImportStatement { + const stmt: ImportStatement = new ImportStatement(); + stmt.range = range; + for (let i: i32 = 0, k: i32 = (stmt.declarations = declarations).length; i < k; ++i) declarations[i].parent = stmt; + stmt.path = path; + return stmt; + } + + static createImportDeclaration(externalIdentifier: IdentifierExpression, identifier: IdentifierExpression | null, range: Range): ImportDeclaration { + const elem: ImportDeclaration = new ImportDeclaration(); + elem.range = range; + (elem.identifier = identifier ? identifier : externalIdentifier).parent = elem; + (elem.externalIdentifier = externalIdentifier).parent = elem; + return elem; + } + + static createInterface(modifiers: Modifier[], extendsType: TypeNode | null, members: Statement[], range: Range): InterfaceDeclaration { + const stmt: InterfaceDeclaration = new InterfaceDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + if (stmt.extendsType = extendsType) (extendsType).parent = stmt; + for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; + return stmt; + } + + static createField(modifiers: Modifier[], identifier: IdentifierExpression, type: TypeNode | null, initializer: Expression | null, range: Range): FieldDeclaration { + const stmt: FieldDeclaration = new FieldDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + (stmt.identifier = identifier).parent = stmt; + if (stmt.type = type) (type).parent = stmt; + if (stmt.initializer = initializer) (initializer).parent = stmt; + return stmt; + } + + static createFor(initializer: Statement | null, condition: Expression | null, incrementor: Expression | null, statement: Statement, range: Range): ForStatement { + const stmt: ForStatement = new ForStatement(); + stmt.range = range; + if (stmt.initializer = initializer) (initializer).parent = stmt; + if (stmt.condition = condition) (condition).parent = stmt; + if (stmt.incrementor = incrementor) (incrementor).parent = stmt; + (stmt.statement = statement).parent = stmt; + return stmt; + } + + static createTypeParameter(identifier: IdentifierExpression, extendsType: TypeNode | null, range: Range): TypeParameter { + const elem: TypeParameter = new TypeParameter(); + elem.range = range; + (elem.identifier = identifier).parent = elem; + if (elem.extendsType = extendsType) (extendsType).parent = elem; + return elem; + } + + static createParameter(identifier: IdentifierExpression, type: TypeNode | null, initializer: Expression | null, multiple: bool, range: Range): Parameter { + const elem: Parameter = new Parameter(); + elem.range = range; + (elem.identifier = identifier).parent = elem; + if (elem.type = type) (type).parent = elem; + if (elem.initializer = initializer) (initializer).parent = elem; + elem.multiple = multiple; + return elem; + } + + static createFunction(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], parameters: Parameter[], returnType: TypeNode | null, statements: Statement[] | null, range: Range): FunctionDeclaration { + const stmt: FunctionDeclaration = new FunctionDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + (stmt.identifier = identifier).parent = stmt; + for (i = 0, k = (stmt.typeParameters = typeParameters).length; i < k; ++i) typeParameters[i].parent = stmt; + 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; + return stmt; + } + + static createMethod(modifiers: Modifier[], identifier: IdentifierExpression, typeParameters: TypeParameter[], parameters: Parameter[], returnType: TypeNode | null, statements: Statement[] | null, range: Range): MethodDeclaration { + const stmt: MethodDeclaration = new MethodDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + (stmt.identifier = identifier).parent = stmt; + for (i = 0, k = (stmt.typeParameters = typeParameters).length; i < k; ++i) typeParameters[i].parent = stmt; + 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; + return stmt; + } + + static createModifier(kind: ModifierKind, range: Range): Modifier { + const elem: Modifier = new Modifier(); + elem.range = range; + elem.modifierKind = kind; + return elem; + } + + static createNamespace(modifiers: Modifier[], identifier: IdentifierExpression, members: DeclarationStatement[], range: Range): NamespaceDeclaration { + const stmt: NamespaceDeclaration = new NamespaceDeclaration(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.modifiers = modifiers).length; i < k; ++i) modifiers[i].parent = stmt; + (stmt.identifier = identifier).parent = stmt; + for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; + return stmt; + } + + static createReturn(expression: Expression | null, range: Range): ReturnStatement { + const stmt: ReturnStatement = new ReturnStatement(); + stmt.range = range; + if (stmt.expression = expression) (expression).parent = stmt; + return stmt; + } + + static createSwitch(expression: Expression, cases: SwitchCase[], range: Range): SwitchStatement { + const stmt: SwitchStatement = new SwitchStatement(); + stmt.range = range; + (stmt.expression = expression).parent = stmt; + for (let i: i32 = 0, k: i32 = (stmt.cases = cases).length; i < k; ++i) cases[i].parent = stmt; + return stmt; + } + + static createSwitchCase(label: Expression | null, statements: Statement[], range: Range): SwitchCase { + const elem: SwitchCase = new SwitchCase(); + elem.range = range; + if (elem.label = label) (label).parent = elem; + for (let i: i32 = 0, k: i32 = (elem.statements = statements).length; i < k; ++i) statements[i].parent = elem; + return elem; + } + + static createThrow(expression: Expression, range: Range): ThrowStatement { + const stmt: ThrowStatement = new ThrowStatement(); + stmt.range = range; + (stmt.expression = expression).parent = stmt; + return stmt; + } + + static createTry(statements: Statement[], catchVariable: VariableDeclaration, catchStatements: Statement[], range: Range): TryStatement { + const stmt: TryStatement = new TryStatement(); + stmt.range = range; + let i: i32, k: i32; + for (i = 0, k = (stmt.statements = statements).length; i < k; ++i) statements[i].parent = stmt; + (stmt.catchVariable = catchVariable).parent = stmt; + for (i = 0, k = (stmt.catchStatements = catchStatements).length; i < k; ++i) catchStatements[i].parent = stmt; + return stmt; + } + + static createVariable(modifiers: Modifier[], declarations: VariableDeclaration[], range: Range): VariableStatement { + const stmt: VariableStatement = new VariableStatement(); + 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; + return stmt; + } + + static createVariableDeclaration(identifier: IdentifierExpression, type: TypeNode | null, initializer: Expression | null, range: Range): VariableDeclaration { + const elem: VariableDeclaration = new VariableDeclaration(); + elem.range = range; + (elem.identifier = identifier).parent = elem; + if (elem.type = type) (type).parent = elem; + if (elem.initializer = initializer) (initializer).parent = elem; + return elem; + } + + static createWhile(condition: Expression, statement: Statement, range: Range): WhileStatement { + const stmt: WhileStatement = new WhileStatement(); + stmt.range = range; + (stmt.condition = condition).parent = stmt; + (stmt.statement = statement).parent = stmt; + return stmt; + } +} + +export abstract class SourceNode extends Node { // extended by Source + + kind = NodeKind.SOURCE; + parent = null; + path: string; + statements: Statement[]; + + serialize(sb: string[]): void { + for (let i: i32 = 0, k = this.statements.length; i < k; ++i) { + const statement: Statement = this.statements[i]; + statement.serialize(sb); + const last: string = sb[sb.length - 1]; + if (last.charCodeAt(last.length - 1) == CharCode.CLOSEBRACE) + sb.push("\n"); + else + sb.push(";\n"); + } + } +} + +export abstract class DeclarationStatement extends Statement { + identifier: IdentifierExpression; + reflectionIndex: i32 = -1; +} + +export class BlockStatement extends Statement { + + kind = NodeKind.BLOCK; + statements: Statement[]; + + serialize(sb: string[]): void { + sb.push("{\n"); + for (let i: i32 = 0; i < this.statements.length; ++i) { + this.statements[i].serialize(sb); + if (endsWith(CharCode.CLOSEBRACE, sb)) + sb.push("\n"); + else + sb.push(";\n"); + } + sb.push("}"); + } +} + +export class BreakStatement extends Statement { + + kind = NodeKind.BREAK; + label: IdentifierExpression | null; + + serialize(sb: string[]): void { + if (this.label) { + sb.push("break "); + sb.push((this.label).name); + } else + sb.push("break"); + } +} + +export class ClassDeclaration extends DeclarationStatement { + + kind = NodeKind.CLASS; + modifiers: Modifier[]; + typeParameters: TypeParameter[]; + extendsType: TypeNode | null; + implementsTypes: TypeNode[]; + members: DeclarationStatement[]; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + } + sb.push("class "); + sb.push(this.identifier.name); + if (this.typeParameters.length) { + sb.push("<"); + for (let i: i32 = 0; i < this.typeParameters.length; ++i) { + if (i > 0) + sb.push(", "); + this.typeParameters[i].serialize(sb); + } + sb.push(">"); + } + if (this.extendsType) { + sb.push(" extends "); + this.extendsType.serialize(sb); + } + if (this.implementsTypes.length) { + sb.push(" implements "); + for (let i: i32 = 0; i < this.implementsTypes.length; ++i) { + if (i > 0) + sb.push(", "); + this.implementsTypes[i].serialize(sb); + } + } + sb.push(" {\n"); + for (let i: i32 = 0; i < this.members.length; ++i) { + this.members[i].serialize(sb); + if (endsWith(CharCode.CLOSEBRACE, sb)) + sb.push("\n"); + else + sb.push(";\n"); + } + sb.push("}"); + } +} + +export class ContinueStatement extends Statement { + + kind = NodeKind.CONTINUE; + label: IdentifierExpression | null; + + serialize(sb: string[]): void { + if (this.label) { + sb.push("continue "); + sb.push(this.label.name); + } else + sb.push("continue"); + } +} + +export class DoStatement extends Statement { + + kind = NodeKind.DO; + statement: Statement; + condition: Expression; + + serialize(sb: string[]): void { + sb.push("do "); + this.statement.serialize(sb); + if (this.statement.kind == NodeKind.BLOCK) + sb.push(" while ("); + else + sb.push(";\nwhile ("); + this.condition.serialize(sb); + sb.push(")"); + } +} + +export class EmptyStatement extends Statement { + + kind = NodeKind.EMPTY; + + serialize(sb: string[]): void {} +} + +export class EnumDeclaration extends DeclarationStatement { + + kind = NodeKind.ENUM; + modifiers: Modifier[]; + members: EnumValueDeclaration[]; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++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) { + if (i > 0) + sb.push(",\n"); + this.members[i].serialize(sb); + } + sb.push("\n}"); + } +} + +export class EnumValueDeclaration extends DeclarationStatement { + + kind = NodeKind.ENUMVALUE; + value: Expression | null; + + serialize(sb: string[]): void { + this.identifier.serialize(sb); + if (this.value) { + sb.push(" = "); + (this.value).serialize(sb); + } + } +} + +export class ExportImportStatement extends Node { + + kind = NodeKind.EXPORTIMPORT; + identifier: IdentifierExpression; + asIdentifier: IdentifierExpression; + + serialize(sb: string[]): void { + sb.push("export import "); + this.asIdentifier.serialize(sb); + sb.push(" = "); + this.identifier.serialize(sb); + } +} + +export class ExportMember extends Node { + + identifier: IdentifierExpression; + asIdentifier: IdentifierExpression | null; + + serialize(sb: string[]): void { + this.identifier.serialize(sb); + if (this.asIdentifier) { + sb.push(" as "); + (this.asIdentifier).serialize(sb); + } + } +} + +export class ExportStatement extends Statement { + + kind = NodeKind.EXPORT; + modifiers: Modifier[]; + members: ExportMember[]; + path: string | null; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + } + sb.push("export {\n"); + for (let i: i32 = 0; i < this.members.length; ++i) { + if (i > 0) + sb.push(",\n"); + this.members[i].serialize(sb); + } + if (this.path == null) + sb.push("\n}"); + else { + sb.push("\n} from "); + sb.push(JSON.stringify(this.path)); + } + } +} + +export class ExpressionStatement extends Statement { + + kind = NodeKind.EXPRESSION; + expression: Expression; + + serialize(sb: string[]): void { + this.expression.serialize(sb); + } +} + +export class FieldDeclaration extends DeclarationStatement { + + kind = NodeKind.FIELD; + modifiers: Modifier[]; + type: TypeNode | null; + initializer: Expression | null; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + } + this.identifier.serialize(sb); + if (this.type) { + sb.push(": "); + (this.type).serialize(sb); + } + if (this.initializer) { + sb.push(" = "); + (this.initializer).serialize(sb); + } + } +} + +export class ForStatement extends Statement { + + kind = NodeKind.FOR; + initializer: Statement | null; + condition: Expression | null; + incrementor: Expression | null; + statement: Statement; + + serialize(sb: string[]): void { + sb.push("for ("); + if (this.initializer) + this.initializer.serialize(sb); + if (this.condition) { + sb.push("; "); + this.condition.serialize(sb); + } else + sb.push(";"); + if (this.incrementor) { + sb.push("; "); + this.incrementor.serialize(sb); + } else + sb.push(";"); + sb.push(") "); + this.statement.serialize(sb); + } +} + +export class FunctionDeclaration extends DeclarationStatement { + + kind = NodeKind.FUNCTION; + modifiers: Modifier[]; + typeParameters: TypeParameter[]; + parameters: Parameter[]; + returnType: TypeNode | null; + statements: Statement[] | null; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + } + sb.push("function "); + this.serializeCommon(sb); + } + + protected serializeCommon(sb: string[]): void { + this.identifier.serialize(sb); + if (this.typeParameters.length) { + sb.push("<"); + for (let i: i32 = 0; i < this.typeParameters.length; ++i) { + if (i > 0) + sb.push(", "); + this.typeParameters[i].serialize(sb); + } + sb.push(">"); + } + sb.push("("); + for (let i: i32 = 0; i < this.parameters.length; ++i) { + if (i > 0) + sb.push(", "); + this.parameters[i].serialize(sb); + } + if (this.returnType) { + sb.push("): "); + (this.returnType).serialize(sb); + } else + sb.push(")"); + if (this.statements) { + sb.push(" {\n"); + for (let i: i32 = 0; i < (this.statements).length; ++i) { + const statement: Statement = (this.statements)[i]; + statement.serialize(sb); + if (endsWith(CharCode.CLOSEBRACE, sb)) + sb.push("\n"); + else + sb.push(";\n"); + } + sb.push("}"); + } + } +} + +export class IfStatement extends Statement { + + kind = NodeKind.IF; + condition: Expression; + statement: Statement; + elseStatement: Statement | null; + + serialize(sb: string[]): void { + sb.push("if ("); + this.condition.serialize(sb); + sb.push(") "); + this.statement.serialize(sb); + if (this.statement.kind != NodeKind.BLOCK) + sb.push(";\n"); + if (this.elseStatement) { + if (this.statement.kind == NodeKind.BLOCK) + sb.push(" else "); + else + sb.push("else "); + (this.elseStatement).serialize(sb); + } + } +} + +export class ImportDeclaration extends DeclarationStatement { + + kind = NodeKind.IMPORTDECLARATION; + externalIdentifier: IdentifierExpression; + + serialize(sb: string[]): void { + this.externalIdentifier.serialize(sb); + if (this.externalIdentifier != this.identifier) { + sb.push(" as "); + (this.identifier).serialize(sb); + } + } +} + +export class ImportStatement extends Statement { + + kind = NodeKind.IMPORT; + declarations: ImportDeclaration[]; + path: string; + + serialize(sb: string[]): void { + sb.push("import {\n"); + for (let i: i32 = 0; i < this.declarations.length; ++i) { + if (i > 0) + sb.push(",\n"); + this.declarations[i].serialize(sb); + } + sb.push("\n} from "); + sb.push(JSON.stringify(this.path)); + } +} + +export class InterfaceDeclaration extends DeclarationStatement { + + kind = NodeKind.INTERFACE; + modifiers: Modifier[]; + typeParameters: TypeParameter[]; + extendsType: TypeNode | null; + members: Statement[]; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + } + sb.push("interface "); + this.identifier.serialize(sb); + if (this.typeParameters.length) { + sb.push("<"); + for (let i: i32 = 0; i < this.typeParameters.length; ++i) { + if (i > 0) + sb.push(", "); + this.typeParameters[i].serialize(sb); + } + sb.push(">"); + } + if (this.extendsType) { + sb.push(" extends "); + (this.extendsType).serialize(sb); + } + sb.push(" {\n"); + for (let i: i32 = 0; i < this.members.length; ++i) { + this.members[i].serialize(sb); + if (endsWith(CharCode.CLOSEBRACE, sb)) + sb.push("\n"); + else + sb.push(";\n"); + } + sb.push("}"); + } +} + +export class MethodDeclaration extends FunctionDeclaration { + + kind = NodeKind.METHOD; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + } + super.serializeCommon(sb); + } +} + +export class NamespaceDeclaration extends DeclarationStatement { + + kind = NodeKind.NAMESPACE; + modifiers: Modifier[]; + members: DeclarationStatement[]; + + serialize(sb: string[]): void { + for (let i: i32 = 0; i < this.modifiers.length; ++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) { + this.members[i].serialize(sb); + if (endsWith(CharCode.CLOSEBRACE, sb)) + sb.push("\n"); + else + sb.push(";\n"); + } + sb.push("}"); + } +} + +export class Parameter extends Node { + + identifier: IdentifierExpression; + type: TypeNode | null; + initializer: Expression | null; + multiple: bool; + + serialize(sb: string[]): void { + if (this.multiple) + sb.push("..."); + this.identifier.serialize(sb); + if (this.type) { + sb.push(": "); + (this.type).serialize(sb); + } + if (this.initializer) { + sb.push(" = "); + (this.initializer).serialize(sb); + } + } +} + +export class Modifier extends Node { + + modifierKind: ModifierKind; + + serialize(sb: string[]): void { + switch (this.modifierKind) { + case ModifierKind.ABSTRACT: sb.push("abstract"); break; + case ModifierKind.ASYNC: sb.push("async"); break; + case ModifierKind.CONST: sb.push("const"); break; + case ModifierKind.DECLARE: sb.push("declare"); break; + case ModifierKind.EXPORT: sb.push("export"); break; + case ModifierKind.GET: sb.push("get"); break; + case ModifierKind.IMPORT: sb.push("import"); break; + case ModifierKind.PRIVATE: sb.push("private"); break; + case ModifierKind.PROTECTED: sb.push("protected"); break; + case ModifierKind.PUBLIC: sb.push("public"); break; + case ModifierKind.SET: sb.push("set"); break; + case ModifierKind.STATIC: sb.push("static"); break; + default: sb.push("INVALID"); break; + } + } +} + +export class ReturnStatement extends Statement { + + kind = NodeKind.RETURN; + expression: Expression | null; + + serialize(sb: string[]): void { + if (this.expression) { + sb.push("return "); + (this.expression).serialize(sb); + } else + sb.push("return"); + } +} + +export class SwitchCase extends Node { + + label: Expression | null; // null := default + statements: Statement[]; + + serialize(sb: string[]): void { + if (this.label) { + sb.push("case "); + (this.label).serialize(sb); + sb.push(":\n"); + } else + sb.push("default:\n"); + for (let i: i32 = 0; i < this.statements.length; ++i) { + if (i > 0) + sb.push("\n"); + this.statements[i].serialize(sb); + if (endsWith(CharCode.CLOSEBRACE, sb)) + sb.push("\n"); + else + sb.push(";\n"); + } + } +} + +export class SwitchStatement extends Statement { + + kind = NodeKind.SWITCH; + expression: Expression; + cases: SwitchCase[]; + + serialize(sb: string[]): void { + sb.push("switch ("); + this.expression.serialize(sb); + sb.push(") {\n"); + for (let i: i32 = 0; i < this.cases.length; ++i) { + this.cases[i].serialize(sb); + sb.push("\n"); + } + sb.push("}"); + } +} + +export class ThrowStatement extends Statement { + + kind = NodeKind.THROW; + expression: Expression; + + serialize(sb: string[]): void { + sb.push("throw "); + this.expression.serialize(sb); + } +} + +export class TryStatement extends Statement { + + kind = NodeKind.TRY; + statements: Statement[]; + catchVariable: VariableDeclaration; + catchStatements: Statement[]; + + serialize(sb: string[]): void { + sb.push("try {\n"); + for (let i: i32 = 0; i < this.statements.length; ++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) { + this.catchStatements[i].serialize(sb); + sb.push(";\n"); + } + sb.push("}"); + } +} + +export class VariableDeclaration extends DeclarationStatement { + + kind = NodeKind.VARIABLEDECLARATION; + type: TypeNode | null; + initializer: Expression | null; + + serialize(sb: string[]): void { + this.identifier.serialize(sb); + if (this.type) { + sb.push(": "); + (this.type).serialize(sb); + } + if (this.initializer) { + sb.push(" = "); + (this.initializer).serialize(sb); + } + } +} + +export class VariableStatement extends Statement { + + kind = NodeKind.VARIABLE; + modifiers: Modifier[]; + members: VariableDeclaration[]; + + serialize(sb: string[]): void { + let isConst: bool = false; + for (let i: i32 = 0; i < this.modifiers.length; ++i) { + this.modifiers[i].serialize(sb); + sb.push(" "); + if (this.modifiers[i].modifierKind == ModifierKind.CONST) + isConst = true; + } + if (!isConst) + sb.push("let "); + for (let i: i32 = 0; i < this.members.length; ++i) { + if (i > 0) + sb.push(", "); + this.members[i].serialize(sb); + } + } +} + +export class WhileStatement extends Statement { + + kind = NodeKind.WHILE; + condition: Expression; + statement: Statement; + + serialize(sb: string[]): void { + sb.push("while ("); + this.condition.serialize(sb); + sb.push(") "); + this.statement.serialize(sb); + } +} + +export function isDeclarationStatement(kind: NodeKind): bool { + return kind == NodeKind.CLASS + || kind == NodeKind.ENUM + || kind == NodeKind.ENUMVALUE + || kind == NodeKind.FIELD + || kind == NodeKind.FUNCTION + || kind == NodeKind.METHOD + || kind == NodeKind.NAMESPACE + || kind == NodeKind.VARIABLEDECLARATION; +} + +export function serialize(node: Node, indent: i32 = 0): string { + sb.length = 0; + node.serialize(sb); + return sb.join(""); +} + +function endsWith(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; + } + return false; +} + +function pushIndent(sb: string[], indent: i32): void { + for (let i: i32 = 0; i < indent; ++i) + sb.push(" "); +} diff --git a/src/binaryen.d.ts b/src/binaryen.d.ts new file mode 100644 index 00000000..ef378188 --- /dev/null +++ b/src/binaryen.d.ts @@ -0,0 +1,252 @@ +/* + + Binaryen's C-API. + + The WebAssembly version of the compiler will be linked against Binaryen + compiled to WebAssembly with these functions present in the binary while the + JS version calls them on the global object. + + see: https://github.com/WebAssembly/binaryen/blob/master/src/binaryen-c.h + +*/ + +declare function _malloc(size: usize): usize; +declare function _free(ptr: usize): void; + +declare type BinaryenIndex = u32; + +declare type BinaryenType = u32; + +declare function _BinaryenNone(): BinaryenType; +declare function _BinaryenInt32(): BinaryenType; +declare function _BinaryenInt64(): BinaryenType; +declare function _BinaryenFloat32(): BinaryenType; +declare function _BinaryenFloat64(): BinaryenType; +declare function _BinaryenUndefined(): BinaryenType; + +declare type BinaryenModuleRef = usize; + +declare function _BinaryenModuleCreate(): BinaryenModuleRef; +declare function _BinaryenModuleDispose(module: BinaryenModuleRef): void; + +declare type BinaryenFunctionTypeRef = usize; +declare type CString = usize; +declare type CArray = usize; + +declare function _BinaryenAddFunctionType(module: BinaryenModuleRef, name: CString, result: BinaryenType, paramTypes: CArray, numParams: BinaryenIndex): BinaryenFunctionTypeRef; + +declare type BinaryenLiteral = CArray; + +// LLVM C ABI with `out` being a buffer of 16 bytes receiving the BinaryenLiteral struct +// union value starts at offset 8 due to alignment (?) +declare function _BinaryenLiteralInt32(out: BinaryenLiteral, x: i32): void; +declare function _BinaryenLiteralInt64(out: BinaryenLiteral, x: i32, y: i32): void; +declare function _BinaryenLiteralFloat32(out: BinaryenLiteral, x: f32): void; +declare function _BinaryenLiteralFloat64(out: BinaryenLiteral, x: f64): void; +declare function _BinaryenLiteralFloat32Bits(out: BinaryenLiteral, x: i32): void; +declare function _BinaryenLiteralFloat64Bits(out: BinaryenLiteral, x: i32, y: i32): void; + +declare type BinaryenOp = i32; + +declare function _BinaryenClzInt32(): BinaryenOp; +declare function _BinaryenCtzInt32(): BinaryenOp; +declare function _BinaryenPopcntInt32(): BinaryenOp; +declare function _BinaryenNegFloat32(): BinaryenOp; +declare function _BinaryenAbsFloat32(): BinaryenOp; +declare function _BinaryenCeilFloat32(): BinaryenOp; +declare function _BinaryenFloorFloat32(): BinaryenOp; +declare function _BinaryenTruncFloat32(): BinaryenOp; +declare function _BinaryenNearestFloat32(): BinaryenOp; +declare function _BinaryenSqrtFloat32(): BinaryenOp; +declare function _BinaryenEqZInt32(): BinaryenOp; +declare function _BinaryenClzInt64(): BinaryenOp; +declare function _BinaryenCtzInt64(): BinaryenOp; +declare function _BinaryenPopcntInt64(): BinaryenOp; +declare function _BinaryenNegFloat64(): BinaryenOp; +declare function _BinaryenAbsFloat64(): BinaryenOp; +declare function _BinaryenCeilFloat64(): BinaryenOp; +declare function _BinaryenFloorFloat64(): BinaryenOp; +declare function _BinaryenTruncFloat64(): BinaryenOp; +declare function _BinaryenNearestFloat64(): BinaryenOp; +declare function _BinaryenSqrtFloat64(): BinaryenOp; +declare function _BinaryenEqZInt64(): BinaryenOp; +declare function _BinaryenExtendSInt32(): BinaryenOp; +declare function _BinaryenExtendUInt32(): BinaryenOp; +declare function _BinaryenWrapInt64(): BinaryenOp; +declare function _BinaryenTruncSFloat32ToInt32(): BinaryenOp; +declare function _BinaryenTruncSFloat32ToInt64(): BinaryenOp; +declare function _BinaryenTruncUFloat32ToInt32(): BinaryenOp; +declare function _BinaryenTruncUFloat32ToInt64(): BinaryenOp; +declare function _BinaryenTruncSFloat64ToInt32(): BinaryenOp; +declare function _BinaryenTruncSFloat64ToInt64(): BinaryenOp; +declare function _BinaryenTruncUFloat64ToInt32(): BinaryenOp; +declare function _BinaryenTruncUFloat64ToInt64(): BinaryenOp; +declare function _BinaryenReinterpretFloat32(): BinaryenOp; +declare function _BinaryenReinterpretFloat64(): BinaryenOp; +declare function _BinaryenConvertSInt32ToFloat32(): BinaryenOp; +declare function _BinaryenConvertSInt32ToFloat64(): BinaryenOp; +declare function _BinaryenConvertUInt32ToFloat32(): BinaryenOp; +declare function _BinaryenConvertUInt32ToFloat64(): BinaryenOp; +declare function _BinaryenConvertSInt64ToFloat32(): BinaryenOp; +declare function _BinaryenConvertSInt64ToFloat64(): BinaryenOp; +declare function _BinaryenConvertUInt64ToFloat32(): BinaryenOp; +declare function _BinaryenConvertUInt64ToFloat64(): BinaryenOp; +declare function _BinaryenPromoteFloat32(): BinaryenOp; +declare function _BinaryenDemoteFloat64(): BinaryenOp; +declare function _BinaryenReinterpretInt32(): BinaryenOp; +declare function _BinaryenReinterpretInt64(): BinaryenOp; +declare function _BinaryenAddInt32(): BinaryenOp; +declare function _BinaryenSubInt32(): BinaryenOp; +declare function _BinaryenMulInt32(): BinaryenOp; +declare function _BinaryenDivSInt32(): BinaryenOp; +declare function _BinaryenDivUInt32(): BinaryenOp; +declare function _BinaryenRemSInt32(): BinaryenOp; +declare function _BinaryenRemUInt32(): BinaryenOp; +declare function _BinaryenAndInt32(): BinaryenOp; +declare function _BinaryenOrInt32(): BinaryenOp; +declare function _BinaryenXorInt32(): BinaryenOp; +declare function _BinaryenShlInt32(): BinaryenOp; +declare function _BinaryenShrUInt32(): BinaryenOp; +declare function _BinaryenShrSInt32(): BinaryenOp; +declare function _BinaryenRotLInt32(): BinaryenOp; +declare function _BinaryenRotRInt32(): BinaryenOp; +declare function _BinaryenEqInt32(): BinaryenOp; +declare function _BinaryenNeInt32(): BinaryenOp; +declare function _BinaryenLtSInt32(): BinaryenOp; +declare function _BinaryenLtUInt32(): BinaryenOp; +declare function _BinaryenLeSInt32(): BinaryenOp; +declare function _BinaryenLeUInt32(): BinaryenOp; +declare function _BinaryenGtSInt32(): BinaryenOp; +declare function _BinaryenGtUInt32(): BinaryenOp; +declare function _BinaryenGeSInt32(): BinaryenOp; +declare function _BinaryenGeUInt32(): BinaryenOp; +declare function _BinaryenAddInt64(): BinaryenOp; +declare function _BinaryenSubInt64(): BinaryenOp; +declare function _BinaryenMulInt64(): BinaryenOp; +declare function _BinaryenDivSInt64(): BinaryenOp; +declare function _BinaryenDivUInt64(): BinaryenOp; +declare function _BinaryenRemSInt64(): BinaryenOp; +declare function _BinaryenRemUInt64(): BinaryenOp; +declare function _BinaryenAndInt64(): BinaryenOp; +declare function _BinaryenOrInt64(): BinaryenOp; +declare function _BinaryenXorInt64(): BinaryenOp; +declare function _BinaryenShlInt64(): BinaryenOp; +declare function _BinaryenShrUInt64(): BinaryenOp; +declare function _BinaryenShrSInt64(): BinaryenOp; +declare function _BinaryenRotLInt64(): BinaryenOp; +declare function _BinaryenRotRInt64(): BinaryenOp; +declare function _BinaryenEqInt64(): BinaryenOp; +declare function _BinaryenNeInt64(): BinaryenOp; +declare function _BinaryenLtSInt64(): BinaryenOp; +declare function _BinaryenLtUInt64(): BinaryenOp; +declare function _BinaryenLeSInt64(): BinaryenOp; +declare function _BinaryenLeUInt64(): BinaryenOp; +declare function _BinaryenGtSInt64(): BinaryenOp; +declare function _BinaryenGtUInt64(): BinaryenOp; +declare function _BinaryenGeSInt64(): BinaryenOp; +declare function _BinaryenGeUInt64(): BinaryenOp; +declare function _BinaryenAddFloat32(): BinaryenOp; +declare function _BinaryenSubFloat32(): BinaryenOp; +declare function _BinaryenMulFloat32(): BinaryenOp; +declare function _BinaryenDivFloat32(): BinaryenOp; +declare function _BinaryenCopySignFloat32(): BinaryenOp; +declare function _BinaryenMinFloat32(): BinaryenOp; +declare function _BinaryenMaxFloat32(): BinaryenOp; +declare function _BinaryenEqFloat32(): BinaryenOp; +declare function _BinaryenNeFloat32(): BinaryenOp; +declare function _BinaryenLtFloat32(): BinaryenOp; +declare function _BinaryenLeFloat32(): BinaryenOp; +declare function _BinaryenGtFloat32(): BinaryenOp; +declare function _BinaryenGeFloat32(): BinaryenOp; +declare function _BinaryenAddFloat64(): BinaryenOp; +declare function _BinaryenSubFloat64(): BinaryenOp; +declare function _BinaryenMulFloat64(): BinaryenOp; +declare function _BinaryenDivFloat64(): BinaryenOp; +declare function _BinaryenCopySignFloat64(): BinaryenOp; +declare function _BinaryenMinFloat64(): BinaryenOp; +declare function _BinaryenMaxFloat64(): BinaryenOp; +declare function _BinaryenEqFloat64(): BinaryenOp; +declare function _BinaryenNeFloat64(): BinaryenOp; +declare function _BinaryenLtFloat64(): BinaryenOp; +declare function _BinaryenLeFloat64(): BinaryenOp; +declare function _BinaryenGtFloat64(): BinaryenOp; +declare function _BinaryenGeFloat64(): BinaryenOp; +declare function _BinaryenPageSize(): BinaryenOp; +declare function _BinaryenCurrentMemory(): BinaryenOp; +declare function _BinaryenGrowMemory(): BinaryenOp; +declare function _BinaryenHasFeature(): BinaryenOp; + +declare type BinaryenExpressionRef = usize; + +declare function _BinaryenBlock(module: BinaryenModuleRef, name: CString, children: CArray, numChildren: BinaryenIndex, type: BinaryenType): BinaryenExpressionRef; +declare function _BinaryenIf(module: BinaryenModuleRef, condition: BinaryenExpressionRef, ifTrue: BinaryenExpressionRef, ifFalse: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenLoop(module: BinaryenModuleRef, name: CString, body: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenBreak(module: BinaryenModuleRef, name: CString, condition: BinaryenExpressionRef, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenSwitch(module: BinaryenModuleRef, names: CArray, numNames: BinaryenIndex, defaultName: CString, condition: BinaryenExpressionRef, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenCall(module: BinaryenModuleRef, target: CString, operands: CArray, numOperands: BinaryenIndex, returnType: BinaryenType): BinaryenExpressionRef; +declare function _BinaryenCallImport(module: BinaryenModuleRef, target: CString, operands: CArray, numOperands: BinaryenIndex, returnType: BinaryenType): BinaryenExpressionRef; +declare function _BinaryenCallIndirect(module: BinaryenModuleRef, target: BinaryenExpressionRef, operands: CArray, numOperands: BinaryenIndex, type: CString): BinaryenExpressionRef; +declare function _BinaryenGetLocal(module: BinaryenModuleRef, index: BinaryenIndex, type: BinaryenType): BinaryenExpressionRef; +declare function _BinaryenSetLocal(module: BinaryenModuleRef, index: BinaryenIndex, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenTeeLocal(module: BinaryenModuleRef, index: BinaryenIndex, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenGetGlobal(module: BinaryenModuleRef, name: CString, type: BinaryenType): BinaryenExpressionRef; +declare function _BinaryenSetGlobal(module: BinaryenModuleRef, name: CString, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenLoad(module: BinaryenModuleRef, bytes: u32, signed: i8, offset: u32, align: u32, type: BinaryenType, ptr: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenStore(module: BinaryenModuleRef, bytes: u32, offset: u32, align: u32, ptr: BinaryenExpressionRef, value: BinaryenExpressionRef, type: BinaryenType): BinaryenExpressionRef; +declare function _BinaryenConst(module: BinaryenModuleRef, value: BinaryenLiteral): BinaryenExpressionRef; +declare function _BinaryenUnary(module: BinaryenModuleRef, op: BinaryenOp, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenBinary(module: BinaryenModuleRef, op: BinaryenOp, left: BinaryenExpressionRef, right: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenSelect(module: BinaryenModuleRef, condition: BinaryenExpressionRef, ifTrue: BinaryenExpressionRef, ifFalse: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenDrop(module: BinaryenModuleRef, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenReturn(module: BinaryenModuleRef, value: BinaryenExpressionRef): BinaryenExpressionRef; +declare function _BinaryenHost(module: BinaryenModuleRef, op: BinaryenOp, name: CString | 0, operands: CArray, numOperands: BinaryenIndex): BinaryenExpressionRef; +declare function _BinaryenNop(module: BinaryenModuleRef): BinaryenExpressionRef; +declare function _BinaryenUnreachable(module: BinaryenModuleRef): BinaryenExpressionRef; + +declare function _BinaryenExpressionPrint(expr: BinaryenExpressionRef): void; + +declare type BinaryenFunctionRef = usize; + +declare function _BinaryenAddFunction(module: BinaryenModuleRef, name: CString, type: BinaryenFunctionTypeRef, varTypes: CArray, numVarTypes: BinaryenIndex, body: BinaryenExpressionRef): BinaryenFunctionRef; + +declare type BinaryenImportRef = usize; + +declare function _BinaryenAddImport(module: BinaryenModuleRef, internalName: CString, externalModuleName: CString, externalBaseName: CString, type: BinaryenFunctionTypeRef): BinaryenImportRef; +declare function _BinaryenRemoveImport(module: BinaryenModuleRef, internalName: CString): void; + +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 _BinaryenSetFunctionTable(module: BinaryenModuleRef, funcs: CArray, numFuncs: BinaryenIndex): void; + +declare function _BinaryenSetMemory(module: BinaryenModuleRef, initial: BinaryenIndex, maximum: BinaryenIndex, exportName: CString, segments: CArray>, segmentOffsets: CArray, segmentSizes: CArray, numSegments: BinaryenIndex): void; + +declare function _BinaryenSetStart(module: BinaryenModuleRef, start: BinaryenFunctionRef): void; + +declare function _BinaryenModuleParse(text: CString): BinaryenModuleRef; +declare function _BinaryenModulePrint(module: BinaryenModuleRef): void; +declare function _BinaryenModulePrintAsmjs(module: BinaryenModuleRef): void; +declare function _BinaryenModuleValidate(module: BinaryenModuleRef): i32; +declare function _BinaryenModuleOptimize(module: BinaryenModuleRef): void; +declare function _BinaryenModuleAutoDrop(module: BinaryenModuleRef): void; +declare function _BinaryenModuleWrite(module: BinaryenModuleRef, output: CString, outputSize: usize): usize; +declare function _BinaryenModuleRead(input: CString, inputSize: usize): BinaryenModuleRef; +declare function _BinaryenModuleInterpret(module: BinaryenModuleRef): void; + +declare type RelooperRef = usize; +declare type RelooperBlockRef = usize; + +declare function _RelooperCreate(): RelooperRef; +declare function _RelooperAddBlock(relooper: RelooperRef, code: BinaryenExpressionRef): RelooperBlockRef; +declare function _RelooperAddBranch(from: RelooperBlockRef, to: RelooperBlockRef, condition: BinaryenExpressionRef, code: BinaryenExpressionRef): void; +declare function _RelooperAddBlockWithSwitch(relooper: RelooperRef, code: BinaryenExpressionRef, condition: BinaryenExpressionRef): RelooperBlockRef; +declare function _RelooperAddBranchForSwitch(from: RelooperBlockRef, to: RelooperBlockRef, indexes: CArray, numIndexes: BinaryenIndex, code: BinaryenExpressionRef): void; +declare function _RelooperRenderAndDispose(relooper: RelooperRef, entry: RelooperBlockRef, labelHelper: BinaryenIndex, module: BinaryenModuleRef): BinaryenExpressionRef; + +declare function _BinaryenSetAPITracing(on: i32): void; + +declare function _BinaryenGetFunctionTypeBySignature(module: BinaryenModuleRef, result: BinaryenType, paramTypes: CArray, numParams: BinaryenIndex): BinaryenFunctionTypeRef; diff --git a/src/binaryen.ts b/src/binaryen.ts new file mode 100644 index 00000000..c24e2564 --- /dev/null +++ b/src/binaryen.ts @@ -0,0 +1,585 @@ +import { U64 } from "./util"; +import { Target } from "./compiler"; + +export enum Type { + None = _BinaryenNone(), + I32 = _BinaryenInt32(), + I64 = _BinaryenInt64(), + F32 = _BinaryenFloat32(), + F64 = _BinaryenFloat64(), + Undefined = _BinaryenUndefined() +} + +export enum UnaryOp { + ClzI32 = _BinaryenClzInt32(), + CtzI32 = _BinaryenCtzInt32(), + PopcntI32 = _BinaryenPopcntInt32(), + NegF32 = _BinaryenNegFloat32(), + AbsF32 = _BinaryenAbsFloat32(), + CeilF32 = _BinaryenCeilFloat32(), + FloorF32 = _BinaryenFloorFloat32(), + TruncF32 = _BinaryenTruncFloat32(), + NearestF32 = _BinaryenNearestFloat32(), + SqrtF32 = _BinaryenSqrtFloat32(), + EqzI32 = _BinaryenEqZInt32(), + ClzI64 = _BinaryenClzInt64(), + CtzI64 = _BinaryenCtzInt64(), + PopcntI64 = _BinaryenPopcntInt64(), + NegF64 = _BinaryenNegFloat64(), + AbsF64 = _BinaryenAbsFloat64(), + CeilF64 = _BinaryenCeilFloat64(), + FloorF64 = _BinaryenFloorFloat64(), + TruncF64 = _BinaryenTruncFloat64(), + NearestF64 = _BinaryenNearestFloat64(), + SqrtF64 = _BinaryenSqrtFloat64(), + EqzI64 = _BinaryenEqZInt64(), + ExtendI32 = _BinaryenExtendSInt32(), + ExtendU32 = _BinaryenExtendUInt32(), + WrapI64 = _BinaryenWrapInt64(), + TruncF32_I32 = _BinaryenTruncSFloat32ToInt32(), + TruncF32_I64 = _BinaryenTruncSFloat32ToInt64(), + TruncF32_U32 = _BinaryenTruncUFloat32ToInt32(), + TruncF32_U64 = _BinaryenTruncUFloat32ToInt64(), + TruncF64_I32 = _BinaryenTruncSFloat64ToInt32(), + TruncF64_I64 = _BinaryenTruncSFloat64ToInt64(), + TruncF64_U32 = _BinaryenTruncUFloat64ToInt32(), + TruncF64_U64 = _BinaryenTruncUFloat64ToInt64(), + ReinterpretF32 = _BinaryenReinterpretFloat32(), + ReinterpretF64 = _BinaryenReinterpretFloat64(), + ConvertI32_F32 = _BinaryenConvertSInt32ToFloat32(), + ConvertI32_F64 = _BinaryenConvertSInt32ToFloat64(), + ConvertU32_F32 = _BinaryenConvertUInt32ToFloat32(), + ConvertU32_F64 = _BinaryenConvertUInt32ToFloat64(), + ConvertI64_F32 = _BinaryenConvertSInt64ToFloat32(), + ConvertI64_F64 = _BinaryenConvertSInt64ToFloat64(), + ConvertU64_F32 = _BinaryenConvertUInt64ToFloat32(), + ConvertU64_F64 = _BinaryenConvertUInt64ToFloat64(), + PromoteF32 = _BinaryenPromoteFloat32(), + DemoteF64 = _BinaryenDemoteFloat64(), + ReinterpretI32 = _BinaryenReinterpretInt32(), + ReinterpretI64 = _BinaryenReinterpretInt64() +} + +export enum BinaryOp { + AddI32 = _BinaryenAddInt32(), + SubI32 = _BinaryenSubInt32(), + MulI32 = _BinaryenMulInt32(), + DivI32 = _BinaryenDivSInt32(), + DivU32 = _BinaryenDivUInt32(), + RemI32 = _BinaryenRemSInt32(), + RemU32 = _BinaryenRemUInt32(), + AndI32 = _BinaryenAndInt32(), + OrI32 = _BinaryenOrInt32(), + XorI32 = _BinaryenXorInt32(), + ShlI32 = _BinaryenShlInt32(), + ShrU32 = _BinaryenShrUInt32(), + ShrI32 = _BinaryenShrSInt32(), + RotlI32 = _BinaryenRotLInt32(), + RotrI32 = _BinaryenRotRInt32(), + EqI32 = _BinaryenEqInt32(), + NeI32 = _BinaryenNeInt32(), + LtI32 = _BinaryenLtSInt32(), + LtU32 = _BinaryenLtUInt32(), + LeI32 = _BinaryenLeSInt32(), + LeU32 = _BinaryenLeUInt32(), + GtI32 = _BinaryenGtSInt32(), + GtU32 = _BinaryenGtUInt32(), + GeI32 = _BinaryenGeSInt32(), + GeU32 = _BinaryenGeUInt32(), + AddI64 = _BinaryenAddInt64(), + SubI64 = _BinaryenSubInt64(), + MulI64 = _BinaryenMulInt64(), + DivI64 = _BinaryenDivSInt64(), + DivU64 = _BinaryenDivUInt64(), + RemI64 = _BinaryenRemSInt64(), + RemU64 = _BinaryenRemUInt64(), + AndI64 = _BinaryenAndInt64(), + OrI64 = _BinaryenOrInt64(), + XorI64 = _BinaryenXorInt64(), + ShlI64 = _BinaryenShlInt64(), + ShrU64 = _BinaryenShrUInt64(), + ShrI64 = _BinaryenShrSInt64(), + RotlI64 = _BinaryenRotLInt64(), + RotrI64 = _BinaryenRotRInt64(), + EqI64 = _BinaryenEqInt64(), + NeI64 = _BinaryenNeInt64(), + LtI64 = _BinaryenLtSInt64(), + LtU64 = _BinaryenLtUInt64(), + LeI64 = _BinaryenLeSInt64(), + LeU64 = _BinaryenLeUInt64(), + GtI64 = _BinaryenGtSInt64(), + GtU64 = _BinaryenGtUInt64(), + GeI64 = _BinaryenGeSInt64(), + GeU64 = _BinaryenGeUInt64(), + AddF32 = _BinaryenAddFloat32(), + SubF32 = _BinaryenSubFloat32(), + MulF32 = _BinaryenMulFloat32(), + DivF32 = _BinaryenDivFloat32(), + CopysignF32 = _BinaryenCopySignFloat32(), + MinF32 = _BinaryenMinFloat32(), + MaxF32 = _BinaryenMaxFloat32(), + EqF32 = _BinaryenEqFloat32(), + NeF32 = _BinaryenNeFloat32(), + LtF32 = _BinaryenLtFloat32(), + LeF32 = _BinaryenLeFloat32(), + GtF32 = _BinaryenGtFloat32(), + GeF32 = _BinaryenGeFloat32(), + AddF64 = _BinaryenAddFloat64(), + SubF64 = _BinaryenSubFloat64(), + MulF64 = _BinaryenMulFloat64(), + DivF64 = _BinaryenDivFloat64(), + CopysignF64 = _BinaryenCopySignFloat64(), + MinF64 = _BinaryenMinFloat64(), + MaxF64 = _BinaryenMaxFloat64(), + EqF64 = _BinaryenEqFloat64(), + NeF64 = _BinaryenNeFloat64(), + LtF64 = _BinaryenLtFloat64(), + LeF64 = _BinaryenLeFloat64(), + GtF64 = _BinaryenGtFloat64(), + GeF64 = _BinaryenGeFloat64() +} + +export enum HostOp { + PageSize = _BinaryenPageSize(), + CurrentMemory = _BinaryenCurrentMemory(), + GrowMemory = _BinaryenGrowMemory(), + HasFeature = _BinaryenHasFeature() +} + +export enum AtomicRMWOp { // TODO: not yet part of the C-API + Add, + Sub, + And, + Or, + Xor, + Xchg +} + +export class MemorySegment { + + buffer: Uint8Array; + offset: U64; + + static create(buffer: Uint8Array, offset: U64) { + const segment: MemorySegment = new MemorySegment(); + segment.buffer = buffer; + segment.offset = offset; + return segment; + } +} + +export class Module { + + ref: BinaryenModuleRef; + lit: BinaryenLiteral; + + static create(): Module { + const module: Module = new Module(); + module.ref = _BinaryenModuleCreate(); + module.lit = _malloc(16); + return module; + } + + static createFrom(buffer: Uint8Array): Module { + const cArr: CArray = allocU8Array(buffer); + try { + const module: Module = new Module(); + module.ref = _BinaryenModuleRead(cArr, buffer.length); + module.lit = _malloc(16); + return module; + } finally { + _free(cArr); + } + } + + static MAX_MEMORY_WASM32: BinaryenIndex = 0xffff; + + // types + + addFunctionType(name: string, result: Type, paramTypes: Type[]): BinaryenFunctionRef { + const cStr: CString = allocString(name); + const cArr: CArray = allocI32Array(paramTypes); + try { + return _BinaryenAddFunctionType(this.ref, cStr, result, cArr, paramTypes.length); + } finally { + _free(cStr); + _free(cArr); + } + } + + getFunctionTypeBySignature(result: Type, paramTypes: Type[]): BinaryenFunctionTypeRef { + const cArr: CArray = allocI32Array(paramTypes); + try { + return _BinaryenGetFunctionTypeBySignature(this.ref, result, cArr, paramTypes.length); + } finally { + _free(cArr); + } + } + + // expressions + + createI32(value: i32): BinaryenExpressionRef { + _BinaryenLiteralInt32(this.lit, value); + return _BinaryenConst(this.ref, this.lit); + } + + createI64(lo: i32, hi: i32): BinaryenExpressionRef { + _BinaryenLiteralInt64(this.lit, lo, hi); + return _BinaryenConst(this.ref, this.lit); + } + + createF32(value: f32): BinaryenExpressionRef { + _BinaryenLiteralFloat32(this.lit, value); + return _BinaryenConst(this.ref, this.lit); + } + + createF64(value: f64): BinaryenExpressionRef { + _BinaryenLiteralFloat64(this.lit, value); + return _BinaryenConst(this.ref, this.lit); + } + + createUnary(op: UnaryOp, expr: BinaryenExpressionRef): BinaryenExpressionRef { + return _BinaryenUnary(this.ref, op, expr); + } + + createBinary(op: BinaryOp, left: BinaryenExpressionRef, right: BinaryenExpressionRef): BinaryenExpressionRef { + return _BinaryenBinary(this.ref, op, left, right); + } + + createHost(op: HostOp, name: string | null = null, operands: BinaryenExpressionRef[] | null = null): BinaryenExpressionRef { + const cStr: CString = allocString(name); + const cArr: CArray = allocI32Array(operands); + try { + return _BinaryenHost(this.ref, op, cStr, cArr, operands ? (operands).length : 0); + } finally { + _free(cStr); + _free(cArr); + } + } + + createGetLocal(index: i32, type: Type): BinaryenExpressionRef { + return _BinaryenGetLocal(this.ref, index, type); + } + + createTeeLocal(index: i32, value: BinaryenExpressionRef): BinaryenExpressionRef { + return _BinaryenTeeLocal(this.ref, index, value); + } + + createGetGlobal(name: string, type: Type): BinaryenExpressionRef { + const cStr: CString = allocString(name); + try { + return _BinaryenGetGlobal(this.ref, cStr, type); + } finally { + _free(cStr); + } + } + + createTeeGlobal(name: string, value: BinaryenExpressionRef, type: Type): BinaryenExpressionRef { + // emulated, lives here for simplicity reasons + return this.createBlock(null, [ + this.createSetGlobal(name, value), + this.createGetGlobal(name, type) + ], type); + } + + // statements + + createSetLocal(index: i32, value: BinaryenExpressionRef): BinaryenExpressionRef { + return _BinaryenSetLocal(this.ref, index, value); + } + + createSetGlobal(name: string, value: BinaryenExpressionRef): BinaryenExpressionRef { + const cStr: CString = allocString(name); + try { + return _BinaryenSetGlobal(this.ref, cStr, value); + } finally { + _free(cStr); + } + } + + createBlock(label: string | null, children: BinaryenExpressionRef[], type: Type = Type.Undefined): BinaryenExpressionRef { + const cStr: CString = allocString(label); + const cArr: CArray = allocI32Array(children); + try { + return _BinaryenBlock(this.ref, cStr, cArr, children.length, type); + } finally { + _free(cStr); + _free(cArr); + } + } + + createBreak(label: string | null, condition: BinaryenExpressionRef = 0, value: BinaryenExpressionRef = 0): BinaryenExpressionRef { + const cStr: CString = allocString(label); + try { + return _BinaryenBreak(this.ref, cStr, condition, value); + } finally { + _free(cStr); + } + } + + createDrop(expression: BinaryenExpressionRef): BinaryenExpressionRef { + return _BinaryenDrop(this.ref, expression); + } + + createLoop(label: string | null, body: BinaryenExpressionRef): BinaryenExpressionRef { + const cStr: CString = allocString(label); + try { + return _BinaryenLoop(this.ref, cStr, body); + } finally { + _free(cStr); + } + } + + createIf(condition: BinaryenExpressionRef, ifTrue: BinaryenExpressionRef, ifFalse: BinaryenExpressionRef = 0): BinaryenExpressionRef { + return _BinaryenIf(this.ref, condition, ifTrue, ifFalse); + } + + createNop(): BinaryenExpressionRef { + return _BinaryenNop(this.ref); + } + + createReturn(expression: BinaryenExpressionRef = 0): BinaryenExpressionRef { + return _BinaryenReturn(this.ref, expression); + } + + createSelect(condition: BinaryenExpressionRef, ifTrue: BinaryenExpressionRef, ifFalse: BinaryenExpressionRef): BinaryenExpressionRef { + return _BinaryenSelect(this.ref, condition, ifTrue, ifFalse); + } + + createSwitch(names: string[], defaultName: string | null, condition: BinaryenExpressionRef, value: BinaryenExpressionRef = 0): BinaryenExpressionRef { + const strs: CString[] = new Array(names.length); + let i: i32, k: i32 = names.length; + for (i = 0; i < k; ++i) strs[i] = allocString(names[i]); + const cArr: CArray = allocI32Array(strs); + const cStr: CString = allocString(defaultName); + try { + return _BinaryenSwitch(this.ref, cArr, k, cStr, condition, value); + } finally { + for (i = 0; i < k; ++i) _free(strs[i]); + _free(cArr); + _free(cStr); + } + } + + createCall(target: BinaryenFunctionRef, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { + const cArr: CArray = allocI32Array(operands); + try { + return _BinaryenCall(this.ref, target, cArr, operands.length, returnType); + } finally { + _free(cArr); + } + } + + createCallImport(target: BinaryenImportRef, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { + const cArr: CArray = allocI32Array(operands); + try { + return _BinaryenCallImport(this.ref, target, cArr, operands.length, returnType); + } finally { + _free(cArr); + } + } + + createUnreachable(): BinaryenExpressionRef { + return _BinaryenUnreachable(this.ref); + } + + // meta + + addGlobal(name: string, type: Type, mutable: bool, initializer: BinaryenExpressionRef): BinaryenImportRef { + const cStr: CString = allocString(name); + try { + return _BinaryenAddGlobal(this.ref, cStr, type, mutable ? 1 : 0, initializer); + } finally { + _free(cStr); + } + } + + addFunction(name: string, type: Type, varTypes: Type[], body: BinaryenExpressionRef): BinaryenFunctionRef { + const cStr: CString = allocString(name); + const cArr: CArray = allocI32Array(varTypes); + try { + return _BinaryenAddFunction(this.ref, cStr, type, cArr, varTypes.length, body); + } finally { + _free(cStr); + _free(cArr); + } + } + + addExport(internalName: string, externalName: string): BinaryenExportRef { + const cStr1: CString = allocString(internalName); + const cStr2: CString = allocString(externalName); + try { + return _BinaryenAddExport(this.ref, cStr1, cStr2); + } finally { + _free(cStr1); + _free(cStr2); + } + } + + removeExport(externalName: string): void { + const cStr = allocString(externalName); + try { + _BinaryenRemoveExport(this.ref, cStr); + } finally { + _free(cStr); + } + } + + addImport(internalName: string, externalModuleName: string, externalBaseName: string, type: BinaryenFunctionTypeRef): BinaryenImportRef { + const cStr1: CString = allocString(internalName); + const cStr2: CString = allocString(externalModuleName); + const cStr3: CString = allocString(externalBaseName); + try { + return _BinaryenAddImport(this.ref, cStr1, cStr2, cStr3, type); + } finally { + _free(cStr1); + _free(cStr2); + _free(cStr3); + } + } + + removeImport(internalName: string): void { + const cStr: CString = allocString(internalName); + try { + _BinaryenRemoveImport(this.ref, cStr); + } finally { + _free(cStr); + } + } + + setMemory(initial: BinaryenIndex, maximum: BinaryenIndex, segments: MemorySegment[], target: Target, exportName: string | null = null): void { + const cStr: CString = allocString(exportName); + let i: i32, k: i32 = segments.length; + const segs: CArray[] = new Array(k); + const offs: BinaryenExpressionRef[] = new Array(k); + const sizs: BinaryenIndex[] = new Array(k); + for (i = 0; i < k; ++i) { + const buffer: Uint8Array = segments[i].buffer; + const offset: U64 = segments[i].offset; + segs[i] = allocU8Array(buffer); + offs[i] = target == Target.WASM64 + ? this.createI64(offset.lo, offset.hi) + : this.createI32(offset.toI32()); + sizs[i] = buffer.length; + } + const cArr1: CArray = allocI32Array(segs); + const cArr2: CArray = allocI32Array(offs); + const cArr3: CArray = allocI32Array(sizs); + try { + _BinaryenSetMemory(this.ref, initial, maximum, cStr, cArr1, cArr2, cArr3, k); + } finally { + _free(cStr); + for (i = 0; i < k; ++i) _free(segs[i]); + _free(cArr1); + _free(cArr2); + _free(cArr3); + } + } + + setStart(func: BinaryenFunctionRef): void { + _BinaryenSetStart(this.ref, func); + } + + optimize(): void { + _BinaryenModuleOptimize(this.ref); + } + + validate(): bool { + return _BinaryenModuleValidate(this.ref) == 1; + } + + dispose(): void { + _BinaryenModuleDispose(this.ref); + _free(this.lit); + } +} + +// helpers +// TODO: investigate stack allocation? + +function allocU8Array(u8s: Uint8Array | null): CArray { + if (!u8s) return 0; + const ptr: usize = _malloc((u8s).length); + let idx: usize = ptr; + for (let i: i32 = 0, k: i32 = (u8s).length; i < k; ++i) + store(idx++, (u8s)[i]) + return ptr; +} + +function allocI32Array(i32s: i32[] | null): CArray { + if (!i32s) return 0; + const ptr: usize = _malloc((i32s).length << 2); + let idx: usize = ptr; + for (let i: i32 = 0, k: i32 = (i32s).length; i < k; ++i) { + let val: i32 = (i32s)[i]; + store(idx , ( val & 0xff) as u8); + store(idx + 1, ((val >> 8) & 0xff) as u8); + store(idx + 2, ((val >> 16) & 0xff) as u8); + store(idx + 3, ( val >>> 24 ) as u8); + idx += 4; + } + return ptr; +} + +function stringLengthUTF8(str: string): usize { + let len: i32 = 0; + for (let i: i32 = 0, k: i32 = str.length; i < k; ++i) { + let u: i32 = str.charCodeAt(i); + if (u >= 0xD800 && u <= 0xDFFF && i + 1 < k) + u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF); + if (u <= 0x7F) + ++len; + else if (u <= 0x7FF) + len += 2; + else if (u <= 0xFFFF) + len += 3; + else if (u <= 0x1FFFFF) + len += 4; + else if (u <= 0x3FFFFFF) + len += 5; + else + len += 6; + } + return len; +} + +function allocString(str: string | null): CString { + if (!str) return 0; + const ptr: usize = _malloc(stringLengthUTF8((str)) + 1); + let idx: usize = ptr; + for (let i: i32 = 0, k = (str).length; i < k; ++i) { + let u: i32 = (str).charCodeAt(i); + if (u >= 0xD800 && u <= 0xDFFF && i + 1 < k) + u = 0x10000 + ((u & 0x3FF) << 10) | ((str).charCodeAt(++i) & 0x3FF); + if (u <= 0x7F) + store(idx++, u as u8); + else if (u <= 0x7FF) { + store(idx++, (0xC0 | (u >>> 6) ) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else if (u <= 0xFFFF) { + store(idx++, (0xE0 | (u >>> 12) ) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else if (u <= 0x1FFFFF) { + store(idx++, (0xF0 | (u >>> 18) ) as u8); + store(idx++, (0x80 | ((u >>> 12) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else if (u <= 0x3FFFFFF) { + store(idx++, (0xF8 | (u >>> 24) ) as u8); + store(idx++, (0x80 | ((u >>> 18) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 12) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } else { + store(idx++, (0xFC | (u >>> 30) ) as u8); + store(idx++, (0x80 | ((u >>> 24) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 18) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 12) & 63)) as u8); + store(idx++, (0x80 | ((u >>> 6) & 63)) as u8); + store(idx++, (0x80 | ( u & 63)) as u8); + } + } + store(idx, 0); + return ptr; +} diff --git a/src/compiler.ts b/src/compiler.ts new file mode 100644 index 00000000..b1c2446b --- /dev/null +++ b/src/compiler.ts @@ -0,0 +1,653 @@ +import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp } from "./binaryen"; +import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; +import { hasModifier } from "./parser"; +import { Program } from "./program"; +import { CharCode, U64 } from "./util"; +import { + + NodeKind, + TypeNode, + + // statements + BlockStatement, + BreakStatement, + ClassDeclaration, + ContinueStatement, + DoStatement, + EmptyStatement, + EnumDeclaration, + ExpressionStatement, + FunctionDeclaration, + ForStatement, + IfStatement, + MethodDeclaration, + ModifierKind, + NamespaceDeclaration, + ReturnStatement, + Statement, + SwitchStatement, + ThrowStatement, + TryStatement, + VariableDeclaration, + VariableStatement, + WhileStatement, + + // expressions + ArrayLiteralExpression, + AssertionExpression, + BinaryExpression, + CallExpression, + ElementAccessExpression, + Expression, + FloatLiteralExpression, + IdentifierExpression, + IntegerLiteralExpression, + LiteralExpression, + LiteralKind, + NewExpression, + ParenthesizedExpression, + PropertyAccessExpression, + SelectExpression, + StringLiteralExpression, + UnaryPostfixExpression, + UnaryPrefixExpression + +} from "./ast"; +import { + + Enum, + Class, + Field, + Function, + GlobalVariable, + LocalVariable, + Namespace, + Method, + Source, + Type, + TypeKind + +} from "./reflection"; + +export enum Target { + WASM32, + WASM64 +} + +export class Options { + target: Target = Target.WASM32; +} + +export class Compiler extends DiagnosticEmitter { + + program: Program; + options: Options; + module: Module; + + currentType: Type = Type.void; + currentClass: Class | null = null; + currentFunction: Function | null = null; + breakMajor: i32 = 0; + breakMinor: i32 = 0; + + memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL + memorySegments: MemorySegment[] = new Array(); + + static compile(program: Program, options: Options | null = null): Module { + const compiler: Compiler = new Compiler(program, options); + return compiler.compile(); + } + + constructor(program: Program, options: Options | null = null) { + super(program.diagnostics); + this.program = program; + this.options = options ? options : new Options(); + this.module = Module.create(); + } + + compile(): Module { + const program: Program = this.program; + + // initialize lookup maps + program.initialize(this.options.target); + + // start by compiling entry file exports + const entrySource: Source = program.sources[0]; + for (let i: i32 = 0, k = entrySource.statements.length; i < k; ++i) { + const statement: Statement = entrySource.statements[i]; + switch (statement.kind) { + + case NodeKind.CLASS: + if (hasModifier(ModifierKind.EXPORT, (statement).modifiers) && !(statement).typeParameters.length) { + const cl: Class = Class.create(statement, []).exportAs((statement).identifier.name); + this.program.addClass(cl); + this.compileClass(cl); + } + 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); + } + 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); + } + 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); + } + 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); + } + } + break; + + case NodeKind.EXPORT: + // obtain referenced declaration and export that + break; + } + } + + // set up memory size and static segments + const initial: U64 = this.memoryOffset.clone(); + const initialOverlaps: U64 = initial.clone(); + initialOverlaps.and32(0xffff); + if (!initialOverlaps.isZero) { + initial.or32(0xffff); + initial.add32(1); + } + initial.shru32(16); + this.module.setMemory(initial.toI32(), Module.MAX_MEMORY_WASM32 /* TODO: not WASM64 compatible yet */, this.memorySegments, this.options.target, "memory"); + + return this.module; + } + + compileClass(cl: Class): void { + throw new Error("not implemented"); + } + + compileEnum(en: Enum): void { + throw new Error("not implemented"); + } + + compileFunction(fn: Function): void { + throw new Error("not implemented"); + } + + compileGlobal(gl: GlobalVariable): void { + throw new Error("not implemented"); + } + + compileNamespace(ns: Namespace): void { + throw new Error("not implemented"); + } + + // memory + + addMemorySegment(buffer: Uint8Array): MemorySegment { + if (this.memoryOffset.lo & 7) { // align to 8 bytes so any possible data type is aligned here + this.memoryOffset.or32(7); + this.memoryOffset.add32(1); + } + const segment: MemorySegment = MemorySegment.create(buffer, this.memoryOffset.clone()); + this.memorySegments.push(segment); + this.memoryOffset.add32(buffer.length); + return segment; + } + + // types + + resolveType(node: TypeNode, reportNotFound: bool = true): Type | null { + const types: Map = this.program.types; + const name: string = node.identifier.name; + if (types.has(name)) { + const type: Type = types.get(name); + return type; + } + if (reportNotFound) + this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, name); + return null; + } + + // statements + + compileStatement(statement: Statement): BinaryenExpressionRef { + switch (statement.kind) { + + case NodeKind.BLOCK: + return this.compileBlockStatement(statement); + + case NodeKind.BREAK: + return this.compileBreakStatement(statement); + + case NodeKind.CONTINUE: + return this.compileContinueStatement(statement); + + case NodeKind.DO: + return this.compileDoStatement(statement); + + case NodeKind.EMPTY: + return this.compileEmptyStatement(statement); + + case NodeKind.EXPRESSION: + return this.compileExpressionStatement(statement); + + case NodeKind.FOR: + return this.compileForStatement(statement); + + case NodeKind.IF: + return this.compileIfStatement(statement); + + case NodeKind.RETURN: + return this.compileReturnStatement(statement); + + case NodeKind.SWITCH: + return this.compileSwitchStatement(statement); + + case NodeKind.THROW: + return this.compileThrowStatement(statement); + + case NodeKind.TRY: + return this.compileTryStatement(statement); + + case NodeKind.VARIABLE: + return this.compileVariableStatement(statement); + + case NodeKind.WHILE: + return this.compileWhileStatement(statement); + } + throw new Error("not implemented"); + } + + compileBlockStatement(statement: BlockStatement): BinaryenExpressionRef { + const substatements: Statement[] = statement.statements; + const children: BinaryenExpressionRef[] = new Array(substatements.length); + for (let i: i32 = 0, k: i32 = substatements.length; i < k; ++i) + children[i] = this.compileStatement(substatements[i]); + return this.module.createBlock(null, children); + } + + compileBreakStatement(statement: BreakStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileContinueStatement(statement: ContinueStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileDoStatement(statement: DoStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileEmptyStatement(statement: EmptyStatement): BinaryenExpressionRef { + return this.module.createNop(); + } + + compileExpressionStatement(statement: ExpressionStatement): BinaryenExpressionRef { + const expression: BinaryenExpressionRef = this.compileExpression(statement.expression, Type.void); + return this.currentType == Type.void ? expression : this.module.createDrop(expression); + } + + compileForStatement(statement: ForStatement): BinaryenExpressionRef { + const initializer: BinaryenExpressionRef = statement.initializer ? this.compileStatement(statement.initializer) : 0; + const condition: BinaryenExportRef = statement.condition ? this.compileExpression(statement.condition, Type.i32) : 0; + const incrementor: BinaryenExportRef = statement.incrementor ? this.compileExpression(statement.incrementor, Type.void) : 0; + throw new Error("not implemented"); + } + + compileIfStatement(statement: IfStatement): BinaryenExpressionRef { + const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32); + const ifTrue: BinaryenExpressionRef = this.compileStatement(statement.statement); + const ifFalse: BinaryenExportRef = statement.elseStatement ? this.compileStatement(statement.elseStatement) : 0; + return this.module.createIf(condition, ifTrue, ifFalse); + } + + compileReturnStatement(statement: ReturnStatement): BinaryenExpressionRef { + if (this.currentFunction) { + const expression: BinaryenExpressionRef = statement.expression ? this.compileExpression(statement.expression, (this.currentFunction).returnType) : 0; + return this.module.createReturn(expression); + } + return this.module.createUnreachable(); + } + + compileSwitchStatement(statement: SwitchStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileThrowStatement(statement: ThrowStatement): BinaryenExpressionRef { + return this.module.createUnreachable(); // TODO: waiting for exception-handling spec + } + + compileTryStatement(statement: TryStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileVariableStatement(statement: VariableStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileWhileStatement(statement: WhileStatement): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + // expressions + + compileExpression(expression: Expression, resultType: Type): BinaryenExpressionRef { + this.currentType = resultType; + + let expr: BinaryenExpressionRef; + switch (expression.kind) { + + case NodeKind.ASSERTION: + expr = this.compileAssertionExpression(expression, resultType); + break; + + case NodeKind.BINARY: + expr = this.compileBinaryExpression(expression, resultType); + break; + + case NodeKind.CALL: + expr = this.compileCallExpression(expression, resultType); + break; + + case NodeKind.ELEMENTACCESS: + expr = this.compileElementAccessExpression(expression, resultType); + break; + + case NodeKind.IDENTIFIER: + expr = this.compileIdentifierExpression(expression, resultType); + break; + + case NodeKind.LITERAL: + expr = this.compileLiteralExpression(expression, resultType); + break; + + case NodeKind.NEW: + expr = this.compileNewExpression(expression, resultType); + break; + + case NodeKind.PARENTHESIZED: + expr = this.compileParenthesizedExpression(expression, resultType); + break; + + case NodeKind.PROPERTYACCESS: + expr = this.compilePropertyAccessExpression(expression, resultType); + break; + + case NodeKind.SELECT: + expr = this.compileSelectExpression(expression, resultType); + break; + + case NodeKind.UNARYPOSTFIX: + expr = this.compileUnaryPostfixExpression(expression, resultType); + break; + + case NodeKind.UNARYPREFIX: + expr = this.compileUnaryPrefixExpression(expression, resultType); + break; + + default: + throw new Error("unexpected expression kind"); + } + + if (this.currentType != resultType) { + expr = this.convertExpression(expr, this.currentType, resultType); + this.currentType = resultType; + } + + return expr; + } + + convertExpression(expr: BinaryenExpressionRef, fromType: Type, toType: Type): BinaryenExpressionRef { + + // void to any + if (fromType.kind == TypeKind.VOID) + throw new Error("illegal conversion"); + + // any to void + if (toType.kind == TypeKind.VOID) + return this.module.createDrop(expr); + + const fromFloat: bool = fromType.isAnyFloat; + const toFloat: bool = toType.isAnyFloat; + + const mod: Module = this.module; + let losesInformation: bool = false; + + if (fromFloat) { + + // float to float + if (toFloat) { + if (fromType.kind == TypeKind.F32) { + + // f32 to f64 + if (toType.kind == TypeKind.F64) + expr = mod.createUnary(UnaryOp.PromoteF32, expr); + + // f64 to f32 + } else if (toType.kind == TypeKind.F32) { + losesInformation = true; + expr = mod.createUnary(UnaryOp.DemoteF64, expr); + } + + // float to int + } else { + losesInformation = true; + + // f32 to int + if (fromType.kind == TypeKind.F32) { + if (toType.isSignedInteger) { + if (toType.isLongInteger) + expr = mod.createUnary(UnaryOp.TruncF32_I64, expr); + else { + expr = mod.createUnary(UnaryOp.TruncF32_I32, expr); + if (toType.isSmallInteger) { + expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); + expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); + } + } + } else { + if (toType.isLongInteger) + expr = mod.createUnary(UnaryOp.TruncF32_U64, expr); + else { + expr = mod.createUnary(UnaryOp.TruncF32_U32, expr); + if (toType.isSmallInteger) + expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + } + } + + // f64 to int + } else { + if (toType.isSignedInteger) { + if (toType.isLongInteger) + expr = mod.createUnary(UnaryOp.TruncF64_I64, expr); + else { + expr = mod.createUnary(UnaryOp.TruncF64_I32, expr); + if (toType.isSmallInteger) { + expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); + expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); + } + } + } else { + if (toType.isLongInteger) + expr = mod.createUnary(UnaryOp.TruncF64_U64, expr); + else { + expr = mod.createUnary(UnaryOp.TruncF64_U32, expr); + if (toType.isSmallInteger) + expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + } + } + } + + } + + // int to float + } else if (toFloat) { + + // int to f32 + if (toType.kind == TypeKind.F32) { + if (fromType.isLongInteger) { + losesInformation = true; + if (fromType.isSignedInteger) + expr = mod.createUnary(UnaryOp.ConvertI64_F32, expr); + else + expr = mod.createUnary(UnaryOp.ConvertU64_F32, expr); + } else + if (fromType.isSignedInteger) + expr = mod.createUnary(UnaryOp.ConvertI32_F32, expr); + else + expr = mod.createUnary(UnaryOp.ConvertU32_F32, expr); + + // int to f64 + } else { + if (fromType.isLongInteger) { + losesInformation = true; + if (fromType.isSignedInteger) + expr = mod.createUnary(UnaryOp.ConvertI64_F64, expr); + else + expr = mod.createUnary(UnaryOp.ConvertU64_F64, expr); + } else + if (fromType.isSignedInteger) + expr = mod.createUnary(UnaryOp.ConvertI32_F64, expr); + else + expr = mod.createUnary(UnaryOp.ConvertU32_F64, expr); + } + + // int to int + } else { + if (fromType.isLongInteger) { + + // i64 to i32 + if (!toType.isLongInteger) { + losesInformation = true; + expr = mod.createUnary(UnaryOp.WrapI64, expr); + if (toType.isSmallInteger) { + if (toType.isSignedInteger) { + expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); + expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); + } else + expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + } + } + + // i32 to i64 + } else if (toType.isLongInteger) { + if (toType.isSignedInteger) + expr = mod.createUnary(UnaryOp.ExtendI32, expr); + else + expr = mod.createUnary(UnaryOp.ExtendU32, expr); + + // i32 to smaller/change of signage i32 + } else if (toType.isSmallInteger && (fromType.size > toType.size || (fromType.size == toType.size && fromType.kind != toType.kind))) { + losesInformation = true; + if (toType.isSignedInteger) { + expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); + expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); + } else + expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + } + } + + return expr; + } + + compileAssertionExpression(expression: AssertionExpression, contextualType: Type): BinaryenExpressionRef { + const toType: Type | null = this.resolveType(expression.toType); // reports + if (toType && toType != contextualType) { + const expr: BinaryenExpressionRef = this.compileExpression(expression.expression, toType); + return this.convertExpression(expr, this.currentType, toType); + } + return this.compileExpression(expression.expression, contextualType); + } + + compileBinaryExpression(expression: BinaryExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileCallExpression(expression: CallExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileElementAccessExpression(expression: ElementAccessExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileLiteralExpression(expression: LiteralExpression, contextualType: Type): BinaryenExpressionRef { + switch (expression.literalKind) { + // case LiteralKind.ARRAY: + + case LiteralKind.FLOAT: + if (contextualType == Type.f32) + return this.module.createF32((expression).value); + this.currentType = Type.f64; + return this.module.createF64((expression).value); + + case LiteralKind.INTEGER: + if (contextualType == Type.bool) + return this.module.createI32((expression).value.isOdd ? 1 : 0) + if (contextualType.isLongInteger) + return this.module.createI64((expression).value.lo, (expression).value.hi); + const value: i32 = (expression).value.toI32(); + if (contextualType.isSmallInteger) + return contextualType.isSignedInteger + ? this.module.createI32(value << contextualType.smallIntegerShift >> contextualType.smallIntegerShift) + : this.module.createI32(value & contextualType.smallIntegerMask); + return this.module.createI32(value); + + // case LiteralKind.OBJECT: + // case LiteralKind.REGEXP: + // case LiteralKind.STRING: + } + throw new Error("not implemented"); + } + + compileNewExpression(expression: NewExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileParenthesizedExpression(expression: ParenthesizedExpression, contextualType: Type): BinaryenExpressionRef { + return this.compileExpression(expression.expression, contextualType); + } + + compilePropertyAccessExpression(expression: PropertyAccessExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileSelectExpression(expression: SelectExpression, contextualType: Type): BinaryenExpressionRef { + const condition: BinaryenExpressionRef = this.compileExpression(expression.condition, Type.i32); + const ifThen: BinaryenExpressionRef = this.compileExpression(expression.ifThen, contextualType); + const ifElse: BinaryenExpressionRef = this.compileExpression(expression.ifElse, contextualType); + return this.module.createSelect(condition, ifThen, ifElse); + } + + compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } + + compileUnaryPrefixExpression(expression: UnaryPrefixExpression, contextualType: Type): BinaryenExpressionRef { + throw new Error("not implemented"); + } +} diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts new file mode 100644 index 00000000..caaec340 --- /dev/null +++ b/src/diagnosticMessages.generated.ts @@ -0,0 +1,100 @@ +// code below is generated from diagnosticsMessages.json by scripts/build-diagnostics + +export enum DiagnosticCode { + Conversion_from_type_0_to_1_requires_an_explicit_cast = 100, + Basic_type_0_cannot_be_nullable = 101, + Unterminated_string_literal = 1002, + Identifier_expected = 1003, + _0_expected = 1005, + A_file_cannot_have_a_reference_to_itself = 1006, + Trailing_comma_not_allowed = 1009, + Unexpected_token = 1012, + A_rest_parameter_must_be_last_in_a_parameter_list = 1014, + A_required_parameter_cannot_follow_an_optional_parameter = 1016, + Statements_are_not_allowed_in_ambient_contexts = 1036, + Initializers_are_not_allowed_in_ambient_contexts = 1039, + _0_modifier_cannot_be_used_here = 1042, + Type_parameters_cannot_appear_on_a_constructor_declaration = 1092, + Type_annotation_cannot_appear_on_a_constructor_declaration = 1093, + An_accessor_cannot_have_type_parameters = 1094, + A_set_accessor_cannot_have_a_return_type_annotation = 1095, + Type_parameter_list_cannot_be_empty = 1098, + A_return_statement_can_only_be_used_within_a_function_body = 1108, + Expression_expected = 1109, + Type_expected = 1110, + A_default_clause_cannot_appear_more_than_once_in_a_switch_statement = 1113, + Duplicate_label_0 = 1114, + Octal_literals_are_not_allowed_in_strict_mode = 1121, + Digit_expected = 1124, + Hexadecimal_digit_expected = 1125, + Unexpected_end_of_text = 1126, + Invalid_character = 1127, + _case_or_default_expected = 1130, + String_literal_expected = 1141, + Line_break_not_permitted_here = 1142, + Declaration_expected = 1146, + Unterminated_regular_expression_literal = 1161, + Binary_digit_expected = 1177, + Octal_digit_expected = 1178, + 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, + _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, + Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391 +} + +export function diagnosticCodeToString(code: DiagnosticCode): string { + switch (code) { + case 100: return "Conversion from type '{0}' to '{1}' requires an explicit cast."; + case 101: return "Basic type '{0}' cannot be nullable."; + case 1002: return "Unterminated string literal."; + case 1003: return "Identifier expected."; + case 1005: return "'{0}' expected."; + case 1006: return "A file cannot have a reference to itself."; + case 1009: return "Trailing comma not allowed."; + case 1012: return "Unexpected token."; + case 1014: return "A rest parameter must be last in a parameter list."; + case 1016: return "A required parameter cannot follow an optional parameter."; + case 1036: return "Statements are not allowed in ambient contexts."; + case 1039: return "Initializers are not allowed in ambient contexts."; + case 1042: return "'{0}' modifier cannot be used here."; + case 1092: return "Type parameters cannot appear on a constructor declaration."; + case 1093: return "Type annotation cannot appear on a constructor declaration."; + case 1094: return "An accessor cannot have type parameters."; + case 1095: return "A 'set' accessor cannot have a return type annotation."; + case 1098: return "Type parameter list cannot be empty."; + case 1108: return "A 'return' statement can only be used within a function body."; + case 1109: return "Expression expected."; + case 1110: return "Type expected."; + case 1113: return "A 'default' clause cannot appear more than once in a 'switch' statement."; + case 1114: return "Duplicate label '{0}'."; + case 1121: return "Octal literals are not allowed in strict mode."; + case 1124: return "Digit expected."; + case 1125: return "Hexadecimal digit expected."; + case 1126: return "Unexpected end of text."; + case 1127: return "Invalid character."; + case 1130: return "'case' or 'default' expected."; + case 1141: return "String literal expected."; + case 1142: return "Line break not permitted here."; + case 1146: return "Declaration expected."; + case 1161: return "Unterminated regular expression literal."; + case 1177: return "Binary digit expected."; + case 1178: return "Octal digit expected."; + 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 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 2391: return "Function implementation is missing or not immediately following the declaration."; + default: return ""; + } +} diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json new file mode 100644 index 00000000..518c79dc --- /dev/null +++ b/src/diagnosticMessages.json @@ -0,0 +1,49 @@ +{ + "Conversion from type '{0}' to '{1}' requires an explicit cast.": 100, + "Basic type '{0}' cannot be nullable.": 101, + + "Unterminated string literal.": 1002, + "Identifier expected.": 1003, + "'{0}' expected.": 1005, + "A file cannot have a reference to itself.": 1006, + "Trailing comma not allowed.": 1009, + "Unexpected token.": 1012, + "A rest parameter must be last in a parameter list.": 1014, + "A required parameter cannot follow an optional parameter.": 1016, + "Statements are not allowed in ambient contexts.": 1036, + "Initializers are not allowed in ambient contexts.": 1039, + "'{0}' modifier cannot be used here.": 1042, + "Type parameters cannot appear on a constructor declaration.": 1092, + "Type annotation cannot appear on a constructor declaration.": 1093, + "An accessor cannot have type parameters.": 1094, + "A 'set' accessor cannot have a return type annotation.": 1095, + "Type parameter list cannot be empty.": 1098, + "A 'return' statement can only be used within a function body.": 1108, + "Expression expected.": 1109, + "Type expected.": 1110, + "A 'default' clause cannot appear more than once in a 'switch' statement.": 1113, + "Duplicate label '{0}'.": 1114, + "Octal literals are not allowed in strict mode.": 1121, + "Digit expected.": 1124, + "Hexadecimal digit expected.": 1125, + "Unexpected end of text.": 1126, + "Invalid character.": 1127, + "'case' or 'default' expected.": 1130, + "String literal expected.": 1141, + "Line break not permitted here.": 1142, + "Declaration expected.": 1146, + "Unterminated regular expression literal.": 1161, + "Binary digit expected.": 1177, + "Octal digit expected.": 1178, + "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, + "'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, + "Function implementation is missing or not immediately following the declaration.": 2391 +} diff --git a/src/diagnostics.ts b/src/diagnostics.ts new file mode 100644 index 00000000..5dfb9b14 --- /dev/null +++ b/src/diagnostics.ts @@ -0,0 +1,167 @@ +import { Range } from "./ast"; +import { CharCode, isLineBreak, sb } from "./util"; +import { DiagnosticCode, diagnosticCodeToString } from "./diagnosticMessages.generated"; + +export { DiagnosticCode, diagnosticCodeToString } from "./diagnosticMessages.generated"; + +export enum DiagnosticCategory { + INFO, + WARNING, + ERROR +} + +export function diagnosticCategoryToString(category: DiagnosticCategory): string { + if (category == DiagnosticCategory.INFO) return "INFO"; + if (category == DiagnosticCategory.WARNING) return "WARNING"; + if (category == DiagnosticCategory.ERROR) return "ERROR"; + return ""; +} + +const colorBlue: string = "\u001b[93m"; +const colorYellow: string = "\u001b[93m"; +const colorRed: string = "\u001b[91m"; +const colorReset: string = "\u001b[0m"; + +export function diagnosticCategoryToColor(category: DiagnosticCategory): string { + if (category == DiagnosticCategory.INFO) return colorBlue; + if (category == DiagnosticCategory.WARNING) return colorYellow; + if (category == DiagnosticCategory.ERROR) return colorRed; + return ""; +} + +export class DiagnosticMessage { + + code: i32; + category: DiagnosticCategory; + message: string; + range: Range | null = null; + + constructor(code: i32, category: DiagnosticCategory, message: string) { + this.code = code; + this.category = category; + this.message = message; + } + + static create(code: DiagnosticCode, category: DiagnosticCategory, arg0: string | null = null, arg1: string | null = null): DiagnosticMessage { + let message: string = diagnosticCodeToString(code); + if (arg0 != null) + message = message.replace("{0}", arg0); + if (arg1 != null) + message = message.replace("{1}", arg1); + return new DiagnosticMessage(code, category, message); + } + + static createInfo(code: DiagnosticCode, arg0: string | null = null, arg1: string | null = null): DiagnosticMessage { + return DiagnosticMessage.create(code, DiagnosticCategory.INFO, arg0, arg1); + } + + static createWarning(code: DiagnosticCode, arg0: string | null = null, arg1: string | null = null): DiagnosticMessage { + return DiagnosticMessage.create(code, DiagnosticCategory.WARNING, arg0, arg1); + } + + static createError(code: DiagnosticCode, arg0: string | null = null, arg1: string | null = null): DiagnosticMessage { + return DiagnosticMessage.create(code, DiagnosticCategory.ERROR, arg0, arg1); + } + + withRange(range: Range): this { + this.range = range; + return this; + } +} + +export function formatDiagnosticMessage(message: DiagnosticMessage, useColors: bool = false, showContext: bool = false): string { + // format context first (uses same string builder) + let context: string = ""; + if (message.range && showContext) + context = formatDiagnosticContext(message.range, useColors) + + // general information + sb.length = 0; + if (useColors) sb.push(diagnosticCategoryToColor(message.category)); + sb.push(diagnosticCategoryToString(message.category)); + if (useColors) sb.push(colorReset); + sb.push(" AS"); + sb.push(message.code.toString()); + sb.push(": "); + sb.push(message.message); + + // range information if available + if (message.range) { + const range: Range = message.range; + const text: string = range.source.text; + if (showContext) { + sb.push("\n"); + sb.push(context); + } + sb.push("\n"); + let pos: i32 = range.start; + let line: i32 = 1; + let column: i32 = 0; + while (pos-- > 0) + if (isLineBreak(text.charCodeAt(pos))) + line++; + else if (line == 1) + column++; + sb.push(" in "); + sb.push(range.source.path); + sb.push("("); + sb.push(line.toString()); + sb.push(","); + sb.push(column.toString()); + sb.push(")"); + } + return sb.join(""); +} + +export function formatDiagnosticContext(range: Range, useColors: bool = false): string { + const text: string = range.source.text; + const len: i32 = text.length; + let start: i32 = range.start; + let end: i32 = range.end; + while (start > 0 && !isLineBreak(text.charCodeAt(start - 1))) + start--; + while (end < len && !isLineBreak(text.charCodeAt(end))) + end++; + sb.length = 0; + sb.push("\n "); + sb.push(text.substring(start, end)); + sb.push("\n "); + while (start < range.start) { + sb.push(" "); + start++; + } + if (useColors) sb.push(colorRed); + if (range.start == range.end) { + sb.push("^"); + } else while (start++ < range.end) + sb.push("~"); + if (useColors) sb.push(colorReset); + return sb.join(""); +} + +export abstract class DiagnosticEmitter { + + diagnostics: DiagnosticMessage[]; + + constructor(diagnostics: DiagnosticMessage[] | null = null) { + this.diagnostics = diagnostics ? diagnostics : new Array(); + } + + emitDiagnostic(code: DiagnosticCode, category: DiagnosticCategory, range: Range, arg0: string | null = null, arg1: string | null = null) { + const message: DiagnosticMessage = DiagnosticMessage.create(code, category, arg0, arg1).withRange(range); + this.diagnostics.push(message); + console.log(formatDiagnosticMessage(message, true, true)); // temporary + } + + error(code: DiagnosticCode, range: Range, arg0: string | null = null, arg1: string | null = null): void { + this.emitDiagnostic(code, DiagnosticCategory.ERROR, range, arg0, arg1); + } + + info(code: DiagnosticCode, range: Range, arg0: string | null = null, arg1: string | null = null): void { + this.emitDiagnostic(code, DiagnosticCategory.INFO, range, arg0, arg1); + } + + warning(code: DiagnosticCode, range: Range, arg0: string | null = null, arg1: string | null = null): void { + this.emitDiagnostic(code, DiagnosticCategory.WARNING, range, arg0, arg1); + } +} diff --git a/src/glue/js.d.ts b/src/glue/js.d.ts new file mode 100644 index 00000000..8badbc75 --- /dev/null +++ b/src/glue/js.d.ts @@ -0,0 +1,16 @@ +// Aliased AssemblyScript types. Beware of semantic differences. +declare type i8 = number; +declare type u8 = number; +declare type i16 = number; +declare type u16 = number; +declare type i32 = number; +declare type u32 = number; +declare type isize = number; +declare type usize = number; +declare type f32 = number; +declare type f64 = number; +declare type bool = boolean; + +// Raw memory access (here: Binaryen memory, T=u8) +declare function store(ptr: usize, val: u8): void; +declare function load(ptr: usize): u8; diff --git a/src/glue/js.ts b/src/glue/js.ts new file mode 100644 index 00000000..05a1fd76 --- /dev/null +++ b/src/glue/js.ts @@ -0,0 +1,16 @@ +const globalScope = typeof window !== "undefined" && window + || typeof global !== "undefined" && global + || self; + +globalScope["store"] = function store_u8(ptr, val) { + binaryen.HEAPU8[ptr] = val; +}; + +globalScope["load"] = function load_u8(ptr) { + return binaryen.HEAPU8[ptr]; +}; + +const binaryen = require("binaryen"); +for (const key in binaryen) + if (/^_(?:Binaryen|Relooper|malloc$|free$)/.test(key)) + globalScope[key] = binaryen[key]; diff --git a/src/glue/wasm.ts b/src/glue/wasm.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..769b0820 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,58 @@ +/* + + Exports a C-like API to the embedder. + + [obtain entrySource, entryPath] + parseFile(entrySource, entryPath) -> parser + while nextPath = nextFile(parser) + [obtain nextSource] + parseFile(nextSource, nextPath) + + Checking for errors: + + while diagnostic = nextDiagnostic(parser) + [print] formatDiagnostic(diagnostic, useColors?, showContext?) + if (isError(diagnostic)) + [abort parsing afterwards] + + compile(parser) -> module + +*/ + +import { Module } from "./binaryen"; +import { Compiler } from "./compiler"; +import { DiagnosticMessage, DiagnosticCategory, DiagnosticCode } from "./diagnostics"; +import { Parser } from "./parser"; +import { Program } from "./program"; + +export function parseFile(text: string, path: string, parser: Parser | null = null): Parser { + let isEntry: bool = false; + if (!parser) { + parser = new Parser(); + isEntry = true; + } + parser.parseFile(text, path, isEntry); + return parser; +} + +export function nextFile(parser: Parser): string | null { + return parser.nextFile(); +} + +export function nextDiagnostic(parser: Parser): DiagnosticMessage | null { + const program: Program = parser.program; + if (program.diagnosticsOffset < program.diagnostics.length) + return program.diagnostics[program.diagnosticsOffset++]; + return null; +} + +export function isError(message: DiagnosticMessage): bool { + return message.category == DiagnosticCategory.ERROR; +} + +export function compile(parser: Parser): Module { + const program: Program = parser.finish(); + return Compiler.compile(program); +} + +export { formatDiagnosticMessage as formatDiagnostic } from "./diagnostics"; diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 00000000..caf38bca --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,1543 @@ +/* + + This is a custom parser specifically written for the AssemblyScript subset. It + accepts some of the most common TypeScript-only patterns that it knows an + appropriate error message for but, though it uses TypeScript's codes for + diagnostics, doesn't ultimately aim at full compatibility. + +*/ + +import { Program } from "./program"; +import { Source } from "./reflection"; +import { Tokenizer, Token, Range } from "./tokenizer"; +import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, formatDiagnosticMessage } from "./diagnostics"; +import { I64 } from "./util"; +import { + + NodeKind, + + // types + TypeNode, + + // expressions + AssertionExpression, + AssertionKind, + Expression, + FloatLiteralExpression, + IdentifierExpression, + IntegerLiteralExpression, + LiteralExpression, + LiteralKind, + + // statements + BlockStatement, + ClassDeclaration, + DoStatement, + EnumDeclaration, + EnumValueDeclaration, + ExportImportStatement, + ExportMember, + ExportStatement, + ExpressionStatement, + ForStatement, + FunctionDeclaration, + IfStatement, + ImportDeclaration, + ImportStatement, + MethodDeclaration, + Modifier, + ModifierKind, + DeclarationStatement, + Parameter, + FieldDeclaration, + ReturnStatement, + Statement, + SwitchCase, + SwitchStatement, + ThrowStatement, + TryStatement, + TypeParameter, + VariableStatement, + VariableDeclaration, + WhileStatement + +} from "./ast"; + +export class Parser extends DiagnosticEmitter { + + program: Program; + backlog: string[] = new Array(); + + constructor() { + super(); + this.program = new Program(this.diagnostics); + } + + parseFile(text: string, path: string, isEntry: bool): void { + if (path.startsWith("./")) + path = path.substring(2); + for (let i: i32 = 0, k: i32 = this.program.sources.length; i < k; ++i) + if (this.program.sources[i].path == path) + throw Error("duplicate source"); + + const source: Source = new Source(path, text, isEntry); + this.program.sources.push(source); + + const tn: Tokenizer = new Tokenizer(source, this.program.diagnostics); + source.tokenizer = tn; + + while (!tn.skip(Token.ENDOFFILE)) { + + let modifiers: Modifier[] | null = null; + + if (tn.skip(Token.EXPORT)) + modifiers = addModifier(Statement.createModifier(ModifierKind.EXPORT, tn.range()), modifiers); + + if (tn.skip(Token.DECLARE)) { + modifiers = addModifier(Statement.createModifier(ModifierKind.DECLARE, tn.range()), modifiers); + tn.peek(true); + if (tn.nextTokenOnNewLine) + this.error(DiagnosticCode.Line_break_not_permitted_here, tn.range(tn.pos)); // recoverable, compatibility + } + + tn.mark(); + + let statement: Statement | null = null; + switch (tn.next()) { + + case Token.CONST: + modifiers = addModifier(Statement.createModifier(ModifierKind.CONST, tn.range()), modifiers); + + if (tn.skip(Token.ENUM)) { + statement = this.parseEnum(tn, modifiers ? modifiers : createModifiers()); + break; + } + // fall through + + case Token.VAR: + case Token.LET: + statement = this.parseVariable(tn, modifiers ? modifiers : createModifiers()); + break; + + case Token.ENUM: + statement = this.parseEnum(tn, modifiers ? modifiers : createModifiers()); + break; + + case Token.FUNCTION: + statement = this.parseFunction(tn, modifiers ? modifiers : createModifiers()); + break; + + case Token.ABSTRACT: + if (!tn.skip(Token.CLASS)) { + this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "class"); + break; + } + modifiers = addModifier(Statement.createModifier(ModifierKind.ABSTRACT, tn.range()), modifiers); + // fall through + + case Token.CLASS: + statement = this.parseClass(tn, modifiers ? modifiers : createModifiers()); + break; + + case Token.IMPORT: + if (hasModifier(ModifierKind.EXPORT, modifiers)) { + statement = this.parseExportImport(tn, getModifier(ModifierKind.EXPORT, modifiers).range); + } else + statement = this.parseImport(tn); + if (modifiers) + reusableModifiers = modifiers; + break; + + case Token.TYPE: + // TODO + + default: + if (hasModifier(ModifierKind.EXPORT, modifiers)) { + tn.reset(); + statement = this.parseExport(tn, modifiers ? modifiers : createModifiers()); + } else { + if (modifiers) { + if (hasModifier(ModifierKind.DECLARE, modifiers)) + this.error(DiagnosticCode._0_modifier_cannot_be_used_here, getModifier(ModifierKind.DECLARE, modifiers).range, "declare"); // recoverable + reusableModifiers = modifiers; + } + tn.reset(); + statement = this.parseStatement(tn, true); + } + break; + } + + if (!statement) + return; + statement.parent = source; + source.statements.push(statement); + } + reusableModifiers = null; + } + + nextFile(): string | null { + if (this.backlog.length) { + const filename: string = this.backlog[0]; + for (let i: i32 = 0, k: i32 = this.backlog.length - 1; i < k; ++i) + this.backlog[i] = this.backlog[i + 1]; + this.backlog.length--; + return filename; + } + return null; + } + + finish(): Program { + if (this.backlog.length) + throw new Error("backlog is not empty"); + return this.program; + } + + parseType(tn: Tokenizer, acceptParenthesized: bool = true): TypeNode | null { + // not TypeScript-compatible + const token: Token = tn.next(); + const startPos: i32 = tn.tokenPos; + + // void + if (token == Token.VOID) + return TypeNode.create(Expression.createIdentifier("void", tn.range()), [], false, tn.range(startPos, tn.pos)); + + let type: TypeNode; + + // ( ... ) + if (acceptParenthesized && token == Token.OPENPAREN) { + const innerType: TypeNode | null = this.parseType(tn, false); + if (!innerType) + return null; + if (!tn.skip(Token.CLOSEPAREN)) { + this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "}"); + return null; + } + type = innerType; + type.range.start = startPos; + type.range.end = tn.pos; + + // this + } else if (token == Token.THIS) { + type = TypeNode.create(Expression.createIdentifier("this", tn.range()), [], false, tn.range(startPos, tn.pos)); + + // true, false + } else if (token == Token.TRUE || token == Token.FALSE) { + type = TypeNode.create(Expression.createIdentifier("bool", tn.range()), [], false, tn.range(startPos, tn.pos)); + + // string literal + } else if (token == Token.STRINGLITERAL) { + tn.readString(); + type = TypeNode.create(Expression.createIdentifier("string", tn.range()), [], false, tn.range(startPos, tn.pos)); + + // Name + } else if (token == Token.IDENTIFIER) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + const parameters: TypeNode[] = new Array(); + let nullable: bool = false; + + // Name + if (tn.skip(Token.LESSTHAN)) { + do { + const parameter: TypeNode | null = this.parseType(tn, true); + if (!parameter) + return null; + parameters.push(parameter); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.GREATERTHAN)) { + this.error(DiagnosticCode._0_expected, tn.range(tn.pos), ">"); + return null; + } + } + // ... | null + if (tn.skip(Token.BAR)) { + if (tn.skip(Token.NULL)) { + nullable = true; + } else { + this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "null"); + return null; + } + } + type = TypeNode.create(identifier, parameters, nullable, tn.range(startPos, tn.pos)); + + } else { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + // ... [][] + while (tn.skip(Token.OPENBRACKET)) { + let bracketRange: Range = tn.range(); + if (!tn.skip(Token.CLOSEBRACKET)) { + this.error(DiagnosticCode._0_expected, tn.range(), "]"); + return null; + } + bracketRange = Range.join(bracketRange, tn.range()); + + // ...[] | null + let nullable: bool = false; + if (tn.skip(Token.BAR)) { + if (tn.skip(Token.NULL)) { + nullable = true; + } else { + this.error(DiagnosticCode._0_expected, tn.range(), "null"); + return null; + } + } + type = TypeNode.create(Expression.createIdentifier("Array", bracketRange), [ type ], nullable, tn.range(startPos, tn.pos)); + if (nullable) + break; + } + + return type; + } + + // statements + + parseVariable(tn: Tokenizer, modifiers: Modifier[]): VariableStatement | null { + // at ('const' | 'let' | 'var'): VariableDeclaration (',' VariableDeclaration)* ';'? + const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; + const members: VariableDeclaration[] = new Array(); + const isDeclare = hasModifier(ModifierKind.DECLARE, modifiers); + do { + const member: VariableDeclaration | null = this.parseVariableDeclaration(tn, isDeclare); + if (!member) + return null; + members.push(member); + } while (tn.skip(Token.COMMA)); + + const ret: VariableStatement = Statement.createVariable(modifiers, members, tn.range(startPos, tn.pos)); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseVariableDeclaration(tn: Tokenizer, isDeclare: bool = false): VariableDeclaration | null { + // Identifier (':' Type)? ('=' Expression)? + if (!tn.skip(Token.IDENTIFIER)) { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + + let type: TypeNode | null = null; + if (tn.skip(Token.COLON)) { + type = this.parseType(tn); + } else + this.error(DiagnosticCode.Type_expected, tn.range(tn.pos)); // recoverable + + let initializer: Expression | null = null; + if (tn.skip(Token.EQUALS)) { + if (isDeclare) + this.error(DiagnosticCode.Initializers_are_not_allowed_in_ambient_contexts, tn.range()); // recoverable + initializer = this.parseExpression(tn); + if (!initializer) + return null; + } + return Statement.createVariableDeclaration(identifier, type, initializer, Range.join(identifier.range, tn.range())); + } + + parseEnum(tn: Tokenizer, modifiers: Modifier[]): EnumDeclaration | null { + // at 'enum': Identifier '{' (EnumValueDeclaration (',' EnumValueDeclaration )*)? '}' ';'? + const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; + if (tn.next() != Token.IDENTIFIER) { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + if (tn.next() != Token.OPENBRACE) { + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + const members: EnumValueDeclaration[] = new Array(); + if (!tn.skip(Token.CLOSEBRACE)) { + do { + const member: EnumValueDeclaration | null = this.parseEnumValue(tn); + if (!member) + return null; + members.push(member); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.CLOSEBRACE)) { + this.error(DiagnosticCode._0_expected, tn.range(), "}"); + return null; + } + } + const ret: EnumDeclaration = Statement.createEnum(modifiers, identifier, members, tn.range(startPos, tn.pos)); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseEnumValue(tn: Tokenizer): EnumValueDeclaration | null { + // Identifier ('=' Expression)? + if (!tn.skip(Token.IDENTIFIER)) { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let value: Expression | null = null; + if (tn.skip(Token.EQUALS)) { + value = this.parseExpression(tn, Precedence.COMMA + 1); + if (!value) + return null; + } + return Statement.createEnumValue(identifier, value, Range.join(identifier.range, tn.range())); + } + + parseReturn(tn: Tokenizer): ReturnStatement | null { + // at 'return': Expression | (';' | '}' | ...'\n') + let expr: Expression | null = null; + if (tn.peek(true) != Token.SEMICOLON && tn.nextToken != Token.CLOSEBRACE && !tn.nextTokenOnNewLine) { + expr = this.parseExpression(tn); + if (!expr) + return null; + } + const ret: ReturnStatement = Statement.createReturn(expr, tn.range()); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseTypeParameters(tn: Tokenizer): TypeParameter[] | null { + // at '<': TypeParameter (',' TypeParameter)* '>' + const typeParameters: TypeParameter[] = new Array(); + if (!tn.skip(Token.GREATERTHAN)) { + do { + const typeParameter: TypeParameter | null = this.parseTypeParameter(tn); + if (!typeParameter) + return null; + typeParameters.push(typeParameter); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.GREATERTHAN)) { + this.error(DiagnosticCode._0_expected, tn.range(), ">"); + return null; + } + } else + this.error(DiagnosticCode.Type_parameter_list_cannot_be_empty, tn.range()); // recoverable + return typeParameters; + } + + parseTypeParameter(tn: Tokenizer): TypeParameter | null { + // Identifier ('extends' Type)? + if (tn.next() == Token.IDENTIFIER) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let extendsName: TypeNode | null = null; + if (tn.skip(Token.EXTENDS)) { + extendsName = this.parseType(tn); + if (!extendsName) + return null; + } + return Statement.createTypeParameter(identifier, extendsName, Range.join(identifier.range, tn.range())); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseParameters(tn: Tokenizer): Parameter[] | null { + // at '(': (Parameter (',' Parameter)*)? ')' + const parameters: Parameter[] = new Array(); + if (tn.peek() != Token.CLOSEPAREN) { + do { + const param: Parameter | null = this.parseParameter(tn); + if (!param) + return null; + parameters.push(param); + } while (tn.skip(Token.COMMA)); + } + if (tn.skip(Token.CLOSEPAREN)) + return parameters; + else + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + return null; + } + + parseParameter(tn: Tokenizer): Parameter | null { + // '...'? Identifier (':' Type)? ('=' Expression)? + let multiple: bool = false; + let startRange: Range | null = null; + if (tn.skip(Token.DOT_DOT_DOT)) { + multiple = true; + startRange = tn.range(); + } + if (tn.skip(Token.IDENTIFIER)) { + if (!multiple) + startRange = tn.range(); + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let type: TypeNode | null = null; + if (tn.skip(Token.COLON)) { + type = this.parseType(tn); + if (!type) + return null; + } + let initializer: Expression | null = null; + if (tn.skip(Token.EQUALS)) { + initializer = this.parseExpression(tn); + if (!initializer) + return null; + } + return Statement.createParameter(identifier, type, initializer, multiple, Range.join(startRange, tn.range())); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseFunction(tn: Tokenizer, modifiers: Modifier[]): FunctionDeclaration | null { + // at 'function': Identifier ('<' TypeParameters)? '(' Parameters (':' Type)? '{' Statement* '}' ';'? + const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; + if (!tn.skip(Token.IDENTIFIER)) { + this.error(DiagnosticCode.Identifier_expected, tn.range(tn.pos)); + return null; + } + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let typeParameters: TypeParameter[] | null = null; + if (tn.skip(Token.LESSTHAN)) { + typeParameters = this.parseTypeParameters(tn); + if (!typeParameters) + return null; + } else + typeParameters = new Array(0); + if (!tn.skip(Token.OPENPAREN)) { + this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "("); + return null; + } + const parameters: Parameter[] | null = this.parseParameters(tn); + if (!parameters) + return null; + let returnType: TypeNode | null = null; + if (tn.skip(Token.COLON)) { + returnType = this.parseType(tn); + if (!returnType) + return null; + } else + this.error(DiagnosticCode.Type_expected, tn.range(tn.pos)); // recoverable + const isDeclare: bool = hasModifier(ModifierKind.DECLARE, modifiers); + let statements: Statement[] | null = null; + if (tn.skip(Token.OPENBRACE)) { + statements = new Array(); + if (isDeclare) + this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, tn.range()); // recoverable + while (!tn.skip(Token.CLOSEBRACE)) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + statements.push(statement); + } + } 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)); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseClass(tn: Tokenizer, modifiers: Modifier[]): ClassDeclaration | null { + // at 'class': Identifier ('<' TypeParameters)? ('extends' Type)? ('implements' Type (',' Type)*)? '{' ClassMember* '}' + const startPos: i32 = modifiers.length ? modifiers[0].range.start : tn.tokenPos; + + if (tn.skip(Token.IDENTIFIER)) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let typeParameters: TypeParameter[] | null; + + if (tn.skip(Token.LESSTHAN)) { + typeParameters = this.parseTypeParameters(tn); + if (!typeParameters) + return null; + } else + typeParameters = new Array(0); + + let extendsType: TypeNode | null = null; + if (tn.skip(Token.EXTENDS)) { + extendsType = this.parseType(tn); + if (!extendsType) + return null; + } + + let implementsTypes: TypeNode[] = new Array(); + if (tn.skip(Token.IMPLEMENTS)) { + do { + const type: TypeNode | null = this.parseType(tn); + if (!type) + return null; + implementsTypes.push(type); + } while (tn.skip(Token.COMMA)); + } + + if (tn.skip(Token.OPENBRACE)) { + const members: DeclarationStatement[] = new Array(); + if (!tn.skip(Token.CLOSEBRACE)) { + const isDeclare = hasModifier(ModifierKind.DECLARE, modifiers); + do { + const member: DeclarationStatement | null = this.parseClassMember(tn, isDeclare); + if (!member) + return null; + members.push(member); + } while (!tn.skip(Token.CLOSEBRACE)); + } + return Statement.createClass(modifiers, identifier, typeParameters, extendsType, implementsTypes, members, tn.range(startPos, tn.pos)); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseClassMember(tn: Tokenizer, parentIsDeclare: bool): DeclarationStatement | null { + // ('public' | 'private' | 'protected')? ('static' | 'abstract')? ('get' | 'set')? Identifier ... + const startRange: Range = tn.range(); + + let modifiers: Modifier[] | null = null; + + if (tn.skip(Token.PUBLIC)) + modifiers = addModifier(Statement.createModifier(ModifierKind.PUBLIC, tn.range()), modifiers); + else if (tn.skip(Token.PRIVATE)) + modifiers = addModifier(Statement.createModifier(ModifierKind.PRIVATE, tn.range()), modifiers); + else if (tn.skip(Token.PROTECTED)) + modifiers = addModifier(Statement.createModifier(ModifierKind.PROTECTED, tn.range()), modifiers); + + if (tn.skip(Token.STATIC)) + modifiers = addModifier(Statement.createModifier(ModifierKind.STATIC, tn.range()), modifiers); + else if (tn.skip(Token.ABSTRACT)) + modifiers = addModifier(Statement.createModifier(ModifierKind.ABSTRACT, tn.range()), modifiers); + + if (tn.skip(Token.GET)) + modifiers = addModifier(Statement.createModifier(ModifierKind.GET, tn.range()), modifiers); + else if (tn.skip(Token.SET)) + modifiers = addModifier(Statement.createModifier(ModifierKind.SET, tn.range()), modifiers); + + if (tn.skip(Token.IDENTIFIER)) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let typeParameters: TypeParameter[] | null; + if (tn.skip(Token.LESSTHAN)) { + typeParameters = this.parseTypeParameters(tn); + if (!typeParameters) + return null; + } else + typeParameters = new Array(0); + + // method: '(' Parameters (':' Type)? '{' Statement* '}' ';'? + if (tn.skip(Token.OPENPAREN)) { + let parameters = this.parseParameters(tn); + if (!parameters) + return null; + let returnType: TypeNode | null = null; + if (tn.skip(Token.COLON)) { + returnType = this.parseType(tn); + if (!returnType) + return null; + } else + this.error(DiagnosticCode.Type_expected, tn.range()); // recoverable + let statements: Statement[] | null = null; + if (tn.skip(Token.OPENBRACE)) { + if (parentIsDeclare) + this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, tn.range()); // recoverable + statements = new Array(); + while (!tn.skip(Token.CLOSEBRACE)) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + statements.push(statement); + } + } else { + if (!parentIsDeclare) + 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())); + tn.skip(Token.SEMICOLON); + return ret; + + // field: (':' Type)? ('=' Expression)? ';'? + } else { + if (hasModifier(ModifierKind.ABSTRACT, modifiers)) + this.error(DiagnosticCode._0_modifier_cannot_be_used_here, getModifier(ModifierKind.ABSTRACT, modifiers).range, "abstract"); // recoverable + if (hasModifier(ModifierKind.GET, modifiers)) + this.error(DiagnosticCode._0_modifier_cannot_be_used_here, getModifier(ModifierKind.GET, modifiers).range, "get"); // recoverable + if (hasModifier(ModifierKind.SET, modifiers)) + this.error(DiagnosticCode._0_modifier_cannot_be_used_here,getModifier(ModifierKind.SET, modifiers).range, "set"); // recoverable + let type: TypeNode | null = null; + if (tn.skip(Token.COLON)) { + type = this.parseType(tn); + if (!type) + return null; + } else + this.error(DiagnosticCode.Type_expected, tn.range()); // recoverable + let initializer: Expression | null = null; + if (tn.skip(Token.EQUALS)) { + initializer = this.parseExpression(tn); + if (!initializer) + return null; + } + const ret: FieldDeclaration = Statement.createField(modifiers ? modifiers : createModifiers(), identifier, type, initializer, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; + } + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseExport(tn: Tokenizer, modifiers: Modifier[]): ExportStatement | null { + // at 'export': '{' ExportMember (',' ExportMember)* }' ('from' StringLiteral)? ';'? + const startRange: Range = modifiers.length ? modifiers[0].range : tn.range(); + if (tn.skip(Token.OPENBRACE)) { + const members: ExportMember[] = new Array(); + if (!tn.skip(Token.CLOSEBRACE)) { + do { + const member: ExportMember | null = this.parseExportMember(tn); + if (!member) + return null; + members.push(member); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.CLOSEBRACE)) { + this.error(DiagnosticCode._0_expected, tn.range(), "}"); + return null; + } + } + let path: string | null = null; + if (tn.skip(Token.FROM)) { + if (tn.skip(Token.STRINGLITERAL)) + path = tn.readString(); + else { + this.error(DiagnosticCode.String_literal_expected, tn.range()); + return null; + } + } + const ret: ExportStatement = Statement.createExport(modifiers, members, path, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + + parseExportMember(tn: Tokenizer): ExportMember | null { + // Identifier ('as' Identifier)? + if (tn.skip(Token.IDENTIFIER)) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let asIdentifier: IdentifierExpression | null = null; + if (tn.skip(Token.AS)) { + if (tn.skip(Token.IDENTIFIER)) + asIdentifier = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + else { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + } + return Statement.createExportMember(identifier, asIdentifier, asIdentifier ? Range.join(identifier.range, asIdentifier.range) : identifier.range); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseImport(tn: Tokenizer): ImportStatement | null { + // at 'import': '{' (ImportMember (',' ImportMember)*)? '}' 'from' StringLiteral ';'? + const startRange: Range = tn.range(); + if (tn.skip(Token.OPENBRACE)) { + const members: ImportDeclaration[] = new Array(); + if (!tn.skip(Token.CLOSEBRACE)) { + do { + const member: ImportDeclaration | null = this.parseImportDeclaration(tn); + if (!member) + return null; + members.push(member); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.CLOSEBRACE)) { + this.error(DiagnosticCode._0_expected, tn.range(), "}"); + return null; + } + } + if (tn.skip(Token.FROM)) { + if (tn.skip(Token.STRINGLITERAL)) { + const path: string = tn.readString(); + const ret: ImportStatement = Statement.createImport(members, path, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode.String_literal_expected, tn.range()); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "from"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + + parseImportDeclaration(tn: Tokenizer): ImportDeclaration | null { + // Identifier ('as' Identifier)? + if (tn.skip(Token.IDENTIFIER)) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + let asIdentifier: IdentifierExpression | null = null; + if (tn.skip(Token.AS)) { + if (tn.skip(Token.IDENTIFIER)) + asIdentifier = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + else { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + } + return Statement.createImportDeclaration(identifier, asIdentifier, asIdentifier ? Range.join(identifier.range, asIdentifier.range) : identifier.range); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseExportImport(tn: Tokenizer, startRange: Range): ExportImportStatement | null { + // at 'export' 'import': Identifier ('=' Identifier)? ';'? + if (tn.skip(Token.IDENTIFIER)) { + const asIdentifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + if (tn.skip(Token.EQUALS)) { + if (tn.skip(Token.IDENTIFIER)) { + const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + const ret: ExportImportStatement = Statement.createExportImport(identifier, asIdentifier, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "="); + } else + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + + parseStatement(tn: Tokenizer, topLevel: bool = false): Statement | null { + // at previous token + tn.mark(); + const token: Token = tn.next(); + switch (token) { + + case Token.CONST: + return this.parseVariable(tn, [ Statement.createModifier(ModifierKind.CONST, tn.range()) ]); + + case Token.DO: + return this.parseDoStatement(tn); + + case Token.FOR: + return this.parseForStatement(tn); + + case Token.IF: + return this.parseIfStatement(tn); + + case Token.LET: + case Token.VAR: + return this.parseVariable(tn, []); + + case Token.OPENBRACE: + return this.parseBlockStatement(tn, topLevel); + + case Token.RETURN: + if (topLevel) + this.error(DiagnosticCode.A_return_statement_can_only_be_used_within_a_function_body, tn.range()); // recoverable + return this.parseReturn(tn); + + case Token.SEMICOLON: + return Statement.createEmpty(tn.range(tn.tokenPos)); + + case Token.SWITCH: + return this.parseSwitchStatement(tn); + + case Token.THROW: + return this.parseThrowStatement(tn); + + case Token.TRY: + return this.parseTryStatement(tn); + + case Token.WHILE: + return this.parseWhileStatement(tn); + + default: + tn.reset(); + return this.parseExpressionStatement(tn); + } + } + + parseBlockStatement(tn: Tokenizer, topLevel: bool): BlockStatement | null { + // at '{': Statement* '}' ';'? + const startPos: i32 = tn.tokenPos; + const statements: Statement[] = new Array(); + while (!tn.skip(Token.CLOSEBRACE)) { + const statement: Statement | null = this.parseStatement(tn, topLevel); + if (!statement) + return null; + statements.push(statement); + } + const ret: BlockStatement = Statement.createBlock(statements, tn.range(startPos, tn.pos)); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseDoStatement(tn: Tokenizer): DoStatement | null { + // at 'do': Statement 'while' '(' Expression ')' ';'? + const startPos: i32 = tn.tokenPos; + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + if (tn.skip(Token.WHILE)) { + if (tn.skip(Token.OPENPAREN)) { + const condition: Expression | null = this.parseExpression(tn); + if (!condition) + return null; + if (tn.skip(Token.CLOSEPAREN)) { + const ret: DoStatement = Statement.createDo(statement, condition, tn.range(startPos, tn.pos)); + tn.skip(Token.SEMICOLON); + return ret; + } + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "while"); + return null; + } + + parseExpressionStatement(tn: Tokenizer): ExpressionStatement | null { + const expr: Expression | null = this.parseExpression(tn); + if (!expr) + return null; + const ret: ExpressionStatement = Statement.createExpression(expr); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseForStatement(tn: Tokenizer): ForStatement | null { + // at 'for': '(' Statement? Expression? ';' Expression? ')' Statement + const startRange: Range = tn.range(); + if (tn.skip(Token.OPENPAREN)) { + const initializer: Statement | null = this.parseStatement(tn); // skips the semicolon (actually an expression) + if (!initializer) + return null; + if (initializer.kind != NodeKind.EXPRESSION && initializer.kind != NodeKind.VARIABLE) + this.error(DiagnosticCode.Expression_expected, initializer.range); // recoverable + if (tn.token == Token.SEMICOLON) { + const condition: Expression | null = this.parseExpression(tn); + if (!condition) + return null; + if (tn.skip(Token.SEMICOLON)) { + const incrementor: Expression | null = this.parseExpression(tn); + if (!incrementor) + return null; + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + return Statement.createFor(initializer, condition, incrementor, statement, Range.join(startRange, tn.range())); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ";"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ";"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + return null; + } + + parseIfStatement(tn: Tokenizer): IfStatement | null { + // at 'if': '(' Expression ')' Statement ('else' Statement)? + const startRange: Range = tn.range(); + if (tn.skip(Token.OPENPAREN)) { + const condition: Expression | null = this.parseExpression(tn); + if (!condition) + return null; + if (tn.skip(Token.CLOSEPAREN)) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + let elseStatement: Statement | null = null; + if (tn.skip(Token.ELSE)) { + elseStatement = this.parseStatement(tn); + if (!elseStatement) + return null; + } + return Statement.createIf(condition, statement, elseStatement, Range.join(startRange, tn.range())); + } + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + return null; + } + + parseSwitchStatement(tn: Tokenizer): SwitchStatement | null { + // at 'switch': '(' Expression ')' '{' SwitchCase* '}' ';'? + const startPos: i32 = tn.tokenPos; + if (tn.skip(Token.OPENPAREN)) { + const condition: Expression | null = this.parseExpression(tn); + if (!condition) + return null; + if (tn.skip(Token.CLOSEPAREN)) { + if (tn.skip(Token.OPENBRACE)) { + const cases: SwitchCase[] = new Array(0); + while (!tn.skip(Token.CLOSEBRACE)) { + const case_: SwitchCase | null = this.parseSwitchCase(tn); + if (!case_) + return null; + cases.push(case_); + } + const ret: SwitchStatement = Statement.createSwitch(condition, cases, tn.range(startPos, tn.pos)); + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + return null; + } + + parseSwitchCase(tn: Tokenizer): SwitchCase | null { + const startPos: i32 = tn.tokenPos; + + // 'case' Expression ':' Statement* + if (tn.skip(Token.CASE)) { + const label: Expression | null = this.parseExpression(tn); + if (!label) + return null; + if (tn.skip(Token.COLON)) { + const statements: Statement[] = new Array(); + while (tn.peek() != Token.CASE && tn.nextToken != Token.DEFAULT && tn.nextToken != Token.CLOSEBRACE) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + statements.push(statement); + } + return Statement.createSwitchCase(label, statements, tn.range(startPos, tn.pos)); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ":"); + + // 'default' ':' Statement* + } else if (tn.nextToken == Token.DEFAULT) { + tn.next(); + if (tn.skip(Token.COLON)) { + const statements: Statement[] = new Array(); + while (tn.peek() != Token.CASE && tn.nextToken != Token.DEFAULT && tn.nextToken != Token.CLOSEBRACE) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + statements.push(statement); + } + return Statement.createSwitchCase(null, statements, tn.range(startPos, tn.pos)); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ":"); + + } else + this.error(DiagnosticCode._case_or_default_expected, tn.range()); + + return null; + } + + parseThrowStatement(tn: Tokenizer): ThrowStatement | null { + // at 'throw': Expression ';'? + const startPos: i32 = tn.tokenPos; + const expression: Expression | null = this.parseExpression(tn); + if (!expression) + return null; + const ret: ThrowStatement = Statement.createThrow(expression, tn.range(startPos, tn.pos)); + tn.skip(Token.SEMICOLON); + return ret; + } + + parseTryStatement(tn: Tokenizer): TryStatement | null { + // at 'try': '{' Statement* '}' 'catch' '(' VariableMember ')' '{' Statement* '}' ';'? + // TODO: 'finally' '{' Statement* '}' + const startRange: Range = tn.range(); + if (tn.skip(Token.OPENBRACE)) { + const statements: Statement[] = new Array(); + while (!tn.skip(Token.CLOSEBRACE)) { + const stmt: Statement | null = this.parseStatement(tn); + if (!stmt) + return null; + statements.push(stmt); + } + if (tn.skip(Token.CATCH)) { + if (tn.skip(Token.OPENPAREN)) { + const catchVariable: VariableDeclaration | null = this.parseVariableDeclaration(tn); + if (!catchVariable) + return null; + if (tn.skip(Token.CLOSEPAREN)) { + if (tn.skip(Token.OPENBRACE)) { + const catchStatements: Statement[] = new Array(); + while (!tn.skip(Token.CLOSEBRACE)) { + const stmt: Statement | null = this.parseStatement(tn); + if (!stmt) + return null; + catchStatements.push(stmt); + } + const ret: TryStatement = Statement.createTry(statements, catchVariable, catchStatements, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "catch"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "{"); + return null; + } + + parseWhileStatement(tn: Tokenizer): WhileStatement | null { + // at 'while': '(' Expression ')' Statement ';'? + const startRange: Range = tn.range(); + if (tn.skip(Token.OPENPAREN)) { + const expression: Expression | null = this.parseExpression(tn); + if (!expression) + return null; + if (tn.skip(Token.CLOSEPAREN)) { + const statement: Statement | null = this.parseStatement(tn); + if (!statement) + return null; + const ret: WhileStatement = Statement.createWhile(expression, statement, Range.join(startRange, tn.range())); + tn.skip(Token.SEMICOLON); + return ret; + } else + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + } else + this.error(DiagnosticCode._0_expected, tn.range(), "("); + return null; + } + + // expressions + // see: http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing + + parseExpressionPrefix(tn: Tokenizer): Expression | null { + const token: Token = tn.next(); + const startPos: i32 = tn.tokenPos; + + if (token == Token.FALSE) + return Expression.createIdentifier("false", tn.range()); + if (token == Token.NULL) + return Expression.createIdentifier("null", tn.range()); + if (token == Token.TRUE) + return Expression.createIdentifier("true", tn.range()); + + let p: Precedence = determinePrecedencePrefix(token); + if (p != Precedence.INVALID) { + const operand: Expression | null = this.parseExpression(tn, p); + if (!operand) + return null; + + // TODO: SpreadExpression, YieldExpression (currently become unsupported UnaryPrefixExpressions) + + // NewExpression + if (token == Token.NEW) { + if (operand.kind == NodeKind.IDENTIFIER || operand.kind == NodeKind.PROPERTYACCESS) { + const args: Expression[] = new Array(); + if (tn.skip(Token.OPENPAREN)) { + if (tn.peek() != Token.CLOSEPAREN) { + do { + const expr: Expression | null = this.parseExpression(tn, Precedence.COMMA + 1); + if (!expr) + return null; + args.push(expr); + } while (tn.skip(Token.COMMA)); + } + if (!tn.skip(Token.CLOSEPAREN)) { + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + return null; + } + } + return Expression.createNew(operand, [], args, tn.range(startPos, tn.pos)); + } else { + this.error(DiagnosticCode.Identifier_expected, tn.range()); + return null; + } + } + + // UnaryPrefixExpression + return Expression.createUnaryPrefix(token, operand, tn.range(startPos, tn.pos)); + } + + switch (token) { + + // ParenthesizedExpression + case Token.OPENPAREN: { + const expr: Expression | null = this.parseExpression(tn); + if (!expr) + return null; + if (!tn.skip(Token.CLOSEPAREN)) { + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + return null; + } + return Expression.createParenthesized(expr, tn.range(startPos, tn.pos)); + } + + // ArrayLiteralExpression + case Token.OPENBRACKET: { + const elementExpressions: (Expression | null)[] = new Array(); + if (!tn.skip(Token.CLOSEBRACKET)) { + do { + let expr: Expression | null; + if (tn.peek() == Token.COMMA || tn.peek() == Token.CLOSEBRACKET) + expr = null; // omitted + else { + expr = this.parseExpression(tn, Precedence.COMMA + 1); + if (!expr) + return null; + } + elementExpressions.push(expr); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.CLOSEBRACKET)) { + this.error(DiagnosticCode._0_expected, tn.range(), "]"); + return null; + } + } + return Expression.createArrayLiteral(elementExpressions, tn.range(startPos, tn.pos)); + } + + // AssertionExpression (unary prefix) + case Token.LESSTHAN: { + const toType: TypeNode | null = this.parseType(tn); + if (!toType) + return null; + if (!tn.skip(Token.GREATERTHAN)) { + this.error(DiagnosticCode._0_expected, tn.range(), ">"); + return null; + } + const expr: Expression | null = this.parseExpressionPrefix(tn); + if (!expr) + return null; + return Expression.createAssertion(AssertionKind.PREFIX, expr, toType, tn.range(startPos, tn.pos)); + } + + // StringLiteralExpression + case Token.STRINGLITERAL: + return Expression.createStringLiteral(tn.readString(), tn.range(startPos, tn.pos)); + + // IntegerLiteralExpression + case Token.INTEGERLITERAL: + return Expression.createIntegerLiteral(tn.readInteger(), tn.range(startPos, tn.pos)); + + // FloatLiteralExpression + case Token.FLOATLITERAL: + return Expression.createFloatLiteral(tn.readFloat(), tn.range(startPos, tn.pos)); + + // RegexpLiteralExpression + case Token.REGEXPLITERAL: + return Expression.createRegexpLiteral(tn.readRegexp(), tn.range(startPos, tn.pos)); + + // IdentifierExpression + case Token.IDENTIFIER: + return Expression.createIdentifier(tn.readIdentifier(), tn.range(startPos, tn.pos)); + + default: + this.error(DiagnosticCode.Expression_expected, tn.range()); + return null; + } + } + + tryParseTypeArgumentsBeforeArguments(tn: Tokenizer): IdentifierExpression[] | null { + // at '<': Identifier (',' Identifier)* '>' '(' + tn.mark(); + if (!tn.skip(Token.LESSTHAN)) + return null; + + const typeArguments: IdentifierExpression[] = new Array(); + do { + const token: Token = tn.next(); + if (token != Token.IDENTIFIER) { + tn.reset(); + return null; + } + typeArguments.push(Expression.createIdentifier(tn.readIdentifier(), tn.range())); + } while (tn.skip(Token.COMMA)); + if (!(tn.skip(Token.GREATERTHAN) && tn.skip(Token.OPENPAREN))) { + tn.reset(); + return null; + } + return typeArguments; + } + + parseArguments(tn: Tokenizer): Expression[] | null { + // at '(': (Expression (',' Expression)*)? ')' + const args: Expression[] = new Array(); + if (!tn.skip(Token.CLOSEPAREN)) { + do { + const expr: Expression | null = this.parseExpression(tn, Precedence.COMMA + 1); + if (!expr) + return null; + args.push(expr); + } while (tn.skip(Token.COMMA)); + if (!tn.skip(Token.CLOSEPAREN)) { + this.error(DiagnosticCode._0_expected, tn.range(), ")"); + return null; + } + } + return args; + } + + parseExpression(tn: Tokenizer, precedence: Precedence = 0): Expression | null { + let expr: Expression | null = this.parseExpressionPrefix(tn); + if (!expr) + return null; + + const startPos: i32 = expr.range.start; + + // CallExpression + const typeArguments: IdentifierExpression[] | null = this.tryParseTypeArgumentsBeforeArguments(tn); + // there might be better ways to distinguish a LESSTHAN from a CALL + if (typeArguments || tn.skip(Token.OPENPAREN)) { + const args: Expression[] | null = this.parseArguments(tn); + if (!args) + return null; + expr = Expression.createCall(expr, typeArguments ? typeArguments : new Array(0), args, tn.range(startPos, tn.pos)); + } + + let token: Token; + let next: Expression | null = null; + let nextPrecedence: Precedence; + + while ((nextPrecedence = determinePrecedence(token = tn.peek())) >= precedence) { // precedence climbing + tn.next(); + + // AssertionExpression + if (token == Token.AS) { + const toType: TypeNode | null = this.parseType(tn); + if (!toType) + return null; + expr = Expression.createAssertion(AssertionKind.AS, expr, toType, tn.range(startPos, tn.pos)); + + // ElementAccessExpression + } else if (token == Token.OPENBRACKET) { + next = this.parseExpression(tn); // resets precedence + if (!next) + return null; + + if (tn.skip(Token.CLOSEBRACKET)) + expr = Expression.createElementAccess(expr, next, tn.range(startPos, tn.pos)); + else { + this.error(DiagnosticCode._0_expected, tn.range(), "]"); + return null; + } + + // UnaryPostfixExpression + } else if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) { + expr = Expression.createUnaryPostfix(token, expr, tn.range(startPos, tn.pos)); + + // SelectExpression + } else if (token == Token.QUESTION) { + const ifThen: Expression | null = this.parseExpression(tn); + if (!ifThen) + return null; + if (tn.skip(Token.COLON)) { + const ifElse: Expression | null = this.parseExpression(tn); + if (!ifElse) + return null; + expr = Expression.createSelect(expr, ifThen, ifElse, tn.range(startPos, tn.pos)); + } else { + this.error(DiagnosticCode._0_expected, tn.range(), ":"); + return null; + } + + } else { + next = this.parseExpression(tn, isRightAssociative(token) ? nextPrecedence : 1 + nextPrecedence); + if (!next) + return null; + + // PropertyAccessExpression + if (token == Token.DOT) { + if (next.kind == NodeKind.IDENTIFIER) { + expr = Expression.createPropertyAccess(expr, next, tn.range(startPos, tn.pos)); + } else { + this.error(DiagnosticCode.Identifier_expected, next.range); + return null; + } + + // BinaryExpression + } else + expr = Expression.createBinary(token, expr, next, tn.range(startPos, tn.pos)); + } + } + return expr; + } +} + +enum Precedence { + COMMA, + SPREAD, + YIELD, + ASSIGNMENT, + CONDITIONAL, + LOGICAL_OR, + LOGICAL_AND, + BITWISE_OR, + BITWISE_XOR, + BITWISE_AND, + EQUALITY, + RELATIONAL, + SHIFT, + ADDITIVE, + MULTIPLICATIVE, + EXPONENTIATED, + UNARY_PREFIX, + UNARY_POSTFIX, + CALL, + MEMBERACCESS, + GROUPING, + INVALID = -1 +} + +function determinePrecedencePrefix(kind: Token): i32 { + switch (kind) { + + case Token.DOT_DOT_DOT: + return Precedence.SPREAD; + + case Token.YIELD: + return Precedence.YIELD; + + case Token.EXCLAMATION: + case Token.TILDE: + case Token.PLUS: + case Token.MINUS: + case Token.PLUS_PLUS: + case Token.MINUS_MINUS: + case Token.TYPEOF: + case Token.VOID: + case Token.DELETE: + return Precedence.UNARY_PREFIX; + + case Token.NEW: + return Precedence.MEMBERACCESS; + + default: + return Precedence.INVALID; + } +} + +function determinePrecedence(kind: Token): i32 { // non-prefix + switch (kind) { + + case Token.COMMA: + return Precedence.COMMA; + + case Token.EQUALS: + case Token.PLUS_EQUALS: + case Token.MINUS_EQUALS: + case Token.ASTERISK_ASTERISK_EQUALS: + case Token.ASTERISK_EQUALS: + case Token.SLASH_EQUALS: + case Token.PERCENT_EQUALS: + case Token.LESSTHAN_LESSTHAN_EQUALS: + case Token.GREATERTHAN_GREATERTHAN_EQUALS: + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: + case Token.AMPERSAND_EQUALS: + case Token.CARET_EQUALS: + case Token.BAR_EQUALS: + return Precedence.ASSIGNMENT; + + case Token.QUESTION: + return Precedence.CONDITIONAL; + + case Token.BAR_BAR: + return Precedence.LOGICAL_OR; + + case Token.AMPERSAND_AMPERSAND: + return Precedence.LOGICAL_AND; + + case Token.BAR: + return Precedence.BITWISE_OR; + + case Token.CARET: + return Precedence.BITWISE_XOR; + + case Token.AMPERSAND: + return Precedence.BITWISE_AND; + + case Token.EQUALS_EQUALS: + case Token.EXCLAMATION_EQUALS: + case Token.EQUALS_EQUALS_EQUALS: + case Token.EXCLAMATION_EQUALS_EQUALS: + return Precedence.EQUALITY; + + case Token.AS: + case Token.IN: + case Token.INSTANCEOF: + case Token.LESSTHAN: + case Token.GREATERTHAN: + case Token.LESSTHAN_EQUALS: + case Token.GREATERTHAN_EQUALS: + return Precedence.RELATIONAL; + + case Token.LESSTHAN_LESSTHAN: + case Token.GREATERTHAN_GREATERTHAN: + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: + return Precedence.SHIFT; + + case Token.PLUS: + case Token.MINUS: + return Precedence.ADDITIVE; + + case Token.ASTERISK: + case Token.SLASH: + case Token.PERCENT: + return Precedence.MULTIPLICATIVE; + + case Token.ASTERISK_ASTERISK: + return Precedence.EXPONENTIATED; + + case Token.PLUS_PLUS: + case Token.MINUS_MINUS: + return Precedence.UNARY_POSTFIX; + + case Token.DOT: + case Token.NEW: + return Precedence.MEMBERACCESS; + + default: + return Precedence.INVALID; + } +} + +function isRightAssociative(kind: Token): bool { // non-prefix + switch (kind) { + + case Token.EQUALS: + case Token.PLUS_EQUALS: + case Token.MINUS_EQUALS: + case Token.ASTERISK_ASTERISK_EQUALS: + case Token.ASTERISK_EQUALS: + case Token.SLASH_EQUALS: + case Token.PERCENT_EQUALS: + case Token.LESSTHAN_LESSTHAN_EQUALS: + case Token.GREATERTHAN_GREATERTHAN_EQUALS: + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: + case Token.AMPERSAND_EQUALS: + case Token.CARET_EQUALS: + case Token.BAR_EQUALS: + case Token.QUESTION: + case Token.ASTERISK_ASTERISK: + return true; + + default: + return false; + } +} + +let reusableModifiers: Modifier[] | null = null; + +function createModifiers(): Modifier[] { + let ret: Modifier[]; + if (reusableModifiers != null) { + ret = reusableModifiers; + reusableModifiers = null; + } else + ret = new Array(1); + ret.length = 0; + return ret; +} + +function addModifier(modifier: Modifier, modifiers: Modifier[] | null): Modifier[] { + if (modifiers == null) + modifiers = createModifiers(); + modifiers.push(modifier); + return modifiers; +} + +export function hasModifier(kind: ModifierKind, modifiers: Modifier[] | null): bool { + if (modifiers != null) + for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i) + if (modifiers[i].modifierKind == kind) + return true; + return false; +} + +function getModifier(kind: ModifierKind, modifiers: Modifier[]): Modifier { + for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i) + if (modifiers[i].modifierKind == kind) + return modifiers[i]; + throw new Error("no such modifier"); +} diff --git a/src/program.ts b/src/program.ts new file mode 100644 index 00000000..d3f8fa4d --- /dev/null +++ b/src/program.ts @@ -0,0 +1,325 @@ +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 } from "./util"; +import { + + Node, + NodeKind, + SourceNode, + ModifierKind, + + ClassDeclaration, + DeclarationStatement, + EnumDeclaration, + EnumValueDeclaration, + FieldDeclaration, + FunctionDeclaration, + ImportDeclaration, + ImportStatement, + InterfaceDeclaration, + MethodDeclaration, + NamespaceDeclaration, + Statement, + VariableDeclaration, + VariableStatement + +} from "./ast"; + +export class Program extends DiagnosticEmitter { + + sources: Source[]; + diagnosticsOffset: i32 = 0; + target: Target = Target.WASM32; + + names: Map = new Map(); + types: Map = new Map(); + + 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(); + } + + initialize(target: Target): void { + this.target = target; + initializeBasicTypes(this.types, target); + + const importStatements: ImportStatement[] = new Array(); + + // build a lookup map of global 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; + for (let j: i32 = 0, l: i32 = statements.length; j < l; ++j) { + const statement: Statement = statements[j]; + switch (statement.kind) { + + case NodeKind.CLASS: + this.initializeClass(statement); + break; + + case NodeKind.ENUM: + this.initializeEnum(statement); + break; + + case NodeKind.FUNCTION: + this.initializeFunction(statement); + break; + + case NodeKind.IMPORT: + this.initializeImports(statement); + importStatements.push(statement); + break; + + case NodeKind.INTERFACE: + this.initializeInterface(statement); + break; + + case NodeKind.NAMESPACE: + this.initializeNamespace(statement); + break; + + case NodeKind.VARIABLE: + this.initializeVariables(statement); + break; + } + } + } + + // resolve imports to their respective declarations + for (let i: i32 = 0, k: i32 = importStatements.length; i < k; ++i) { + const statement: ImportStatement = importStatements[i]; + const importPath: string = resolvePath(normalizePath(statement.path), statement.range.source.normalizedPath); + const members: ImportDeclaration[] = statement.declarations; + for (let j: i32 = 0, l: i32 = members.length; j < l; ++j){ + const declaration: ImportDeclaration = members[j]; + const importedName: string = declaration.externalIdentifier.name; + for (let m: i32 = 0, n: i32 = this.sources.length; m < n; ++n) { + const source: Source = this.sources[m]; + if (source.path == importPath) { + // TODO + } + } + } + } + } + + initializeClass(declaration: ClassDeclaration): void { + this.addName(declaration); + const members: DeclarationStatement[] = declaration.members; + for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { + const statement: Statement = members[j]; + switch (statement.kind) { + + case NodeKind.FIELD: + this.initializeField(statement); + break; + + case NodeKind.METHOD: + this.initializeMethod(statement); + break; + + default: + throw new Error("unexpected class member"); + } + } + } + + initializeField(declaration: FieldDeclaration): void { + this.addName(declaration); + } + + initializeEnum(declaration: EnumDeclaration): void { + this.addName(declaration); + const members: EnumValueDeclaration[] = declaration.members; + for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) + this.initializeEnumValue(members[i]); + } + + initializeEnumValue(declaration: EnumValueDeclaration): void { + this.addName(declaration); + } + + initializeFunction(declaration: FunctionDeclaration): void { + this.addName(declaration); + } + + initializeImports(statement: ImportStatement): void { + const members: ImportDeclaration[] = statement.declarations; + for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) + this.initializeImport(members[i]); + } + + initializeImport(declaration: ImportDeclaration): void { + this.addName(declaration); + } + + initializeInterface(declaration: InterfaceDeclaration): void { + this.addName(declaration); + const members: Statement[] = declaration.members; + for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { + const statement: Statement = members[j]; + switch (statement.kind) { + + case NodeKind.FIELD: + this.initializeField(statement); + break; + + case NodeKind.METHOD: + this.initializeMethod(statement); + break; + + default: + throw new Error("unexpected interface member"); + } + } + } + + initializeMethod(declaration: MethodDeclaration): void { + this.addName(declaration); + } + + initializeNamespace(declaration: NamespaceDeclaration): void { + this.addName(declaration); + const members: Statement[] = declaration.members; + for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { + const statement: Statement = members[j]; + switch (statement.kind) { + + case NodeKind.CLASS: + this.initializeClass(statement); + break; + + case NodeKind.ENUM: + this.initializeEnum(statement); + break; + + case NodeKind.FUNCTION: + this.initializeFunction(statement); + break; + + case NodeKind.INTERFACE: + this.initializeInterface(statement); + break; + + case NodeKind.NAMESPACE: + this.initializeNamespace(statement); + break; + + case NodeKind.VARIABLE: + this.initializeVariables(statement); + break; + + default: + throw new Error("unexpected namespace member"); + } + } + } + + initializeVariables(statement: VariableStatement): void { + const declarations: VariableDeclaration[] = statement.members; + for (let i: i32 = 0, k = declarations.length; i < k; ++i) { + const declaration: VariableDeclaration = declarations[i]; + this.addName(declaration); + } + } + + addName(declaration: DeclarationStatement): void { + const name: string = this.mangleName(declaration); + if (this.names.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); // recoverable + else + this.names.set(name, declaration); + } + + mangleName(declaration: DeclarationStatement): string { + let name: string = declaration.identifier.name; + let parent: Node | null = declaration.parent; + if (parent) { + switch (parent.kind) { + + case NodeKind.SOURCE: + return (parent).path + "/" + name; + + case NodeKind.CLASS: { + if ( + (declaration.kind == NodeKind.FIELD && !hasModifier(ModifierKind.STATIC, (declaration).modifiers)) || + (declaration.kind == NodeKind.METHOD && !hasModifier(ModifierKind.STATIC, (declaration).modifiers)) + ) + return this.mangleName(parent) + "#" + name; + // otherwise fall through + } + case NodeKind.ENUM: + case NodeKind.ENUMVALUE: + case NodeKind.NAMESPACE: + return this.mangleName(parent) + "." + name; + + case NodeKind.IMPORT: { + const impParent: Node | null = (parent).parent; + if (impParent && impParent.kind == NodeKind.SOURCE) + return (impParent).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; + } + break; + } + } + } + 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) { + types.set("i8", Type.i8); + types.set("i16", Type.i16); + types.set("i32", Type.i32); + types.set("i64", Type.i64); + types.set("isize", target == Target.WASM32 ? Type.isize32 : Type.isize64); + types.set("u8", Type.u8); + types.set("u16", Type.u16); + types.set("u32", Type.u32); + types.set("u64", Type.u64); + types.set("usize", target == Target.WASM32 ? Type.usize32 : Type.usize64); + types.set("bool", Type.bool); + types.set("void", Type.void); +} diff --git a/src/reflection.ts b/src/reflection.ts new file mode 100644 index 00000000..6e355948 --- /dev/null +++ b/src/reflection.ts @@ -0,0 +1,310 @@ +/* + + 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 } 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; + 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(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 new file mode 100644 index 00000000..92b55422 --- /dev/null +++ b/src/tokenizer.ts @@ -0,0 +1,1158 @@ +/* + + This is a modified version of TypeScript's scanner that doesn't perform + as much bookkeeping, simply skips over trivia and provides a more general + mark/reset mechanism for the parser to utilize on ambiguous tokens. + + next() advances the token + peek() peeks for the next token + skip(token) skips over a token if possible + mark() marks at current token + reset() resets to marked state + range() gets the range of the current token + + readFloat() on FLOATLITERAL + readIdentifier() on IDENTIFIER + readInteger() on INTEGERLITERAL + readRegexp() on REGEXPLITERAL // TODO + readString() on STRINGLITERAL + +*/ + +import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, formatDiagnosticMessage } from "./diagnostics"; +import { Source } from "./reflection"; +import { I64, CharCode, isLineBreak } from "./util"; + +export enum Token { + + // keywords + // discarded: ANY, BOOLEAN, NEVER, NUMBER, STRING, SYMBOL, UNDEFINED, LESSTHAN_SLASH + + ABSTRACT, + AS, + ASYNC, + AWAIT, // ES2017 + BREAK, // ES2017 + CASE, // ES2017 + CATCH, // ES2017 + CLASS, // ES2017 + CONST, // ES2017 + CONTINUE, // ES2017 + CONSTRUCTOR, + DEBUGGER, // ES2017 + DECLARE, + DEFAULT, // ES2017 + DELETE, // ES2017 + DO, // ES2017 + ELSE, // ES2017 + ENUM, // ES2017 future + EXPORT, // ES2017 + EXTENDS, // ES2017 + FALSE, // ES + FINALLY, // ES2017 + FOR, // ES2017 + FROM, // AS possible identifier + FUNCTION, // ES2017 + GET, + IF, // ES2017 + IMPLEMENTS, // ES2017 non-lexical + IMPORT, // ES2017 + IN, // ES2017 + INSTANCEOF, // ES2017 + INTERFACE, // ES2017 non-lexical + IS, + KEYOF, + LET, // ES2017 non-lexical + MODULE, // AS possible identifier + NAMESPACE, // AS possible identifier + NEW, // ES2017 + NULL, // ES + OF, + PACKAGE, // ES2017 non-lexical + PRIVATE, // ES2017 non-lexical + PROTECTED, // ES2017 non-lexical + PUBLIC, // ES2017 non-lexical + READONLY, + RETURN, // ES2017 + SET, + STATIC, // ES2017 non-lexical + SUPER, // ES2017 + SWITCH, // ES2017 + THIS, // ES2017 + THROW, // ES2017 + TRUE, // ES + TRY, // ES2017 + TYPE, // AS possible identifier + TYPEOF, // ES2017 + VAR, // ES2017 + VOID, // ES2017 + WHILE, // ES2017 + WITH, // ES2017 + YIELD, // ES2017 + + // punctuation + + OPENBRACE, + CLOSEBRACE, + OPENPAREN, + CLOSEPAREN, + OPENBRACKET, + CLOSEBRACKET, + DOT, + DOT_DOT_DOT, + SEMICOLON, + COMMA, + LESSTHAN, + GREATERTHAN, + LESSTHAN_EQUALS, + GREATERTHAN_EQUALS, + EQUALS_EQUALS, + EXCLAMATION_EQUALS, + EQUALS_EQUALS_EQUALS, + EXCLAMATION_EQUALS_EQUALS, + EQUALS_GREATERTHAN, + PLUS, + MINUS, + ASTERISK_ASTERISK, + ASTERISK, + SLASH, + PERCENT, + PLUS_PLUS, + MINUS_MINUS, + LESSTHAN_LESSTHAN, + GREATERTHAN_GREATERTHAN, + GREATERTHAN_GREATERTHAN_GREATERTHAN, + AMPERSAND, + BAR, + CARET, + EXCLAMATION, + TILDE, + AMPERSAND_AMPERSAND, + BAR_BAR, + QUESTION, + COLON, + EQUALS, + PLUS_EQUALS, + MINUS_EQUALS, + ASTERISK_EQUALS, + ASTERISK_ASTERISK_EQUALS, + SLASH_EQUALS, + PERCENT_EQUALS, + LESSTHAN_LESSTHAN_EQUALS, + GREATERTHAN_GREATERTHAN_EQUALS, + GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS, + AMPERSAND_EQUALS, + BAR_EQUALS, + CARET_EQUALS, + AT, + + // literals + + IDENTIFIER, + STRINGLITERAL, + INTEGERLITERAL, + FLOATLITERAL, + REGEXPLITERAL, + + // meta + + INVALID, + ENDOFFILE +} + +const textToKeywordToken: Map = new Map([ + ["abstract", Token.ABSTRACT], + ["as", Token.AS], + ["async", Token.ASYNC], + ["await", Token.AWAIT], + ["break", Token.BREAK], + ["case", Token.CASE], + ["catch", Token.CATCH], + ["class", Token.CLASS], + ["continue", Token.CONTINUE], + ["const", Token.CONST], + ["constructor", Token.CONSTRUCTOR], + ["debugger", Token.DEBUGGER], + ["declare", Token.DECLARE], + ["default", Token.DEFAULT], + ["delete", Token.DELETE], + ["do", Token.DO], + ["else", Token.ELSE], + ["enum", Token.ENUM], + ["export", Token.EXPORT], + ["extends", Token.EXTENDS], + ["false", Token.FALSE], + ["finally", Token.FINALLY], + ["for", Token.FOR], + ["from", Token.FROM], + ["function", Token.FUNCTION], + ["get", Token.GET], + ["if", Token.IF], + ["implements", Token.IMPLEMENTS], + ["import", Token.IMPORT], + ["in", Token.IN], + ["instanceof", Token.INSTANCEOF], + ["interface", Token.INTERFACE], + ["is", Token.IS], + ["keyof", Token.KEYOF], + ["let", Token.LET], + ["module", Token.MODULE], + ["namespace", Token.NAMESPACE], + ["new", Token.NEW], + ["null", Token.NULL], + ["of", Token.OF], + ["package", Token.PACKAGE], + ["private", Token.PRIVATE], + ["protected", Token.PROTECTED], + ["public", Token.PUBLIC], + ["readonly", Token.READONLY], + ["return", Token.RETURN], + ["set", Token.SET], + ["static", Token.STATIC], + ["super", Token.SUPER], + ["switch", Token.SWITCH], + ["this", Token.THIS], + ["throw", Token.THROW], + ["true", Token.TRUE], + ["try", Token.TRY], + ["type", Token.TYPE], + ["typeof", Token.TYPEOF], + ["var", Token.VAR], + ["void", Token.VOID], + ["while", Token.WHILE], + ["with", Token.WITH], + ["yield", Token.YIELD] +]); + +export function operatorTokenToString(token: Token): string { + switch (token) { + case Token.DELETE: return "delete"; + case Token.IN: return "in"; + case Token.INSTANCEOF: return "instanceof"; + case Token.NEW: return "new"; + case Token.TYPEOF: return "typeof"; + case Token.VOID: return "void"; + case Token.YIELD: return "yield"; + case Token.DOT_DOT_DOT: return "..."; + case Token.COMMA: return ","; + case Token.LESSTHAN: return "<"; + case Token.GREATERTHAN: return ">"; + case Token.LESSTHAN_EQUALS: return "<="; + case Token.GREATERTHAN_EQUALS: return ">="; + case Token.EQUALS_EQUALS: return "=="; + case Token.EXCLAMATION_EQUALS: return "!="; + case Token.EQUALS_EQUALS_EQUALS: return "==="; + case Token.EXCLAMATION_EQUALS_EQUALS: return "!=="; + case Token.PLUS: return "+"; + case Token.MINUS: return "-"; + case Token.ASTERISK_ASTERISK: return "**"; + case Token.ASTERISK: return "*"; + case Token.SLASH: return "/"; + case Token.PERCENT: return "%"; + case Token.PLUS_PLUS: return "++"; + case Token.MINUS_MINUS: return "--"; + case Token.LESSTHAN_LESSTHAN: return "<<"; + case Token.GREATERTHAN_GREATERTHAN: return ">>"; + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: return ">>>"; + case Token.AMPERSAND: return "&"; + case Token.BAR: return "|"; + case Token.CARET: return "^"; + case Token.EXCLAMATION: return "!"; + case Token.TILDE: return "~"; + case Token.AMPERSAND_AMPERSAND: return "&&"; + case Token.BAR_BAR: return "||"; + case Token.EQUALS: return "="; + case Token.PLUS_EQUALS: return "+="; + case Token.MINUS_EQUALS: return "-="; + case Token.ASTERISK_EQUALS: return "*="; + case Token.ASTERISK_ASTERISK_EQUALS: return "**="; + case Token.SLASH_EQUALS: return "/="; + case Token.PERCENT_EQUALS: return "%="; + case Token.LESSTHAN_LESSTHAN_EQUALS: return "<<="; + case Token.GREATERTHAN_GREATERTHAN_EQUALS: return ">>="; + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: return ">>>="; + case Token.AMPERSAND_EQUALS: return "&="; + case Token.BAR_EQUALS: return "|="; + case Token.CARET_EQUALS: return "^="; + default: return "INVALID"; + } +} + +const possibleIdentifiers: Set = new Set([ + "from", + "module", + "namespace", + "type" +]); + +export class Range { + + source: Source; + start: i32; + end: i32; + + constructor(source: Source, start: i32, end: i32) { + this.source = source; + this.start = start; + this.end = end; + } + + static join(a: Range, b: Range): Range { + if (a.source != b.source) + throw new Error("source mismatch"); + return new Range(a.source, a.start < b.start ? a.start : b.start, a.end > b.end ? a.end : b.end); + } +} + +export class Tokenizer extends DiagnosticEmitter { + + source: Source; + end: i32 = 0; + + pos: i32 = 0; + token: Token = -1; + tokenPos: i32 = 0; + + markedPos: i32 = 0; + markedToken: Token = -1; + markedTokenPos: i32 = 0; + + nextToken: Token = -1; + nextTokenOnNewLine: bool = false; + + constructor(source: Source, diagnostics: DiagnosticMessage[] | null = null) { + super(diagnostics); + this.source = source; + this.pos = 0; + this.end = source.text.length; + this.diagnostics = diagnostics ? diagnostics : new Array(); + + const text: string = source.text; + + // skip bom + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.BYTEORDERMARK) + this.pos++; + + // skip shebang + if (this.pos + 1 < this.end && text.charCodeAt(this.pos) == CharCode.HASH && text.charCodeAt(this.pos + 1) == CharCode.EXCLAMATION) { + this.pos += 2; + while (this.pos < this.end && text.charCodeAt(this.pos) != CharCode.LINEFEED) + this.pos++; + // 'next' now starts at lf or eof + } + } + + next(preferIdentifier: bool = false): Token { + this.nextToken = -1; + return this.token = this.unsafeNext(preferIdentifier); + } + + private unsafeNext(preferIdentifier: bool = false): Token { + const text: string = this.source.text; + while (true) { + if (this.pos >= this.end) + return Token.ENDOFFILE; + + this.tokenPos = this.pos; + let c: i32 = text.charCodeAt(this.pos); + switch (c) { + + case CharCode.CARRIAGERETURN: + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.LINEFEED) + this.pos++; + break; + + case CharCode.LINEFEED: + case CharCode.TAB: + case CharCode.VERTICALTAB: + case CharCode.FORMFEED: + case CharCode.SPACE: + this.pos++; + break; + + case CharCode.EXCLAMATION: + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.EXCLAMATION_EQUALS_EQUALS; + } + return Token.EXCLAMATION_EQUALS; + } + return Token.EXCLAMATION; + + case CharCode.DOUBLEQUOTE: + case CharCode.SINGLEQUOTE: + case CharCode.BACKTICK: // TODO + return Token.STRINGLITERAL; // expects a call to readString + + case CharCode.PERCENT: + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.PERCENT_EQUALS; + } + return Token.PERCENT; + + case CharCode.AMPERSAND: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.AMPERSAND) { + this.pos++; + return Token.AMPERSAND_AMPERSAND; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.AMPERSAND_EQUALS; + } + } + return Token.AMPERSAND; + + case CharCode.OPENPAREN: + this.pos++; + return Token.OPENPAREN; + + case CharCode.CLOSEPAREN: + this.pos++; + return Token.CLOSEPAREN; + + case CharCode.ASTERISK: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.ASTERISK_EQUALS; + } + if (text.charCodeAt(this.pos) == CharCode.ASTERISK) { + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.ASTERISK_ASTERISK_EQUALS; + } + return Token.ASTERISK_ASTERISK; + } + } + return Token.ASTERISK; + + case CharCode.PLUS: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.PLUS) { + this.pos++; + return Token.PLUS_PLUS; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.PLUS_EQUALS; + } + } + return Token.PLUS; + + case CharCode.COMMA: + this.pos++; + return Token.COMMA; + + case CharCode.MINUS: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.MINUS) { + this.pos++; + return Token.MINUS_MINUS; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.MINUS_EQUALS; + } + } + return Token.MINUS; + + case CharCode.DOT: + if (++this.pos < this.end) { + if (isDecimalDigit(text.charCodeAt(this.pos))) { + this.pos--; + return Token.FLOATLITERAL; // expects a call to readFloat + } + if (text.charCodeAt(this.pos) == CharCode.DOT && this.pos + 1 < this.end && text.charCodeAt(this.pos + 1) == CharCode.DOT) { + this.pos += 2; + return Token.DOT_DOT_DOT; + } + } + return Token.DOT; + + case CharCode.SLASH: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.SLASH) { // single-line comment + while (++this.pos < this.end) { + if (isLineBreak(text.charCodeAt(this.pos))) + break; + } + continue; + } + if (text.charCodeAt(this.pos) == CharCode.ASTERISK) { // multi-line comment + let closed: bool = false; + while (++this.pos < this.end) { + c = text.charCodeAt(this.pos); + if (c == CharCode.ASTERISK && this.pos + 1 < this.end && text.charCodeAt(this.pos + 1) == CharCode.SLASH) { + this.pos += 2; + closed = true; + break; + } + } + if (!closed) + this.error(DiagnosticCode._0_expected, this.range(this.pos), "*/"); + continue; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.SLASH_EQUALS; + } + } + if (this.testRegexp()) + return Token.REGEXPLITERAL; // expects a call to readRegexp + return Token.SLASH; + + case CharCode._0: + case CharCode._1: + case CharCode._2: + case CharCode._3: + case CharCode._4: + case CharCode._5: + case CharCode._6: + case CharCode._7: + case CharCode._8: + case CharCode._9: + return this.testInteger() + ? Token.INTEGERLITERAL // expects a call to readInteger + : Token.FLOATLITERAL; // expects a call to readFloat + + case CharCode.COLON: + this.pos++; + return Token.COLON; + + case CharCode.SEMICOLON: + this.pos++; + return Token.SEMICOLON; + + case CharCode.LESSTHAN: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.LESSTHAN) { + this.pos++; + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.LESSTHAN_LESSTHAN_EQUALS; + } + return Token.LESSTHAN_LESSTHAN; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.LESSTHAN_EQUALS; + } + } + return Token.LESSTHAN; + + case CharCode.EQUALS: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.EQUALS_EQUALS_EQUALS; + } + return Token.EQUALS_EQUALS; + } + if (text.charCodeAt(this.pos) == CharCode.GREATERTHAN) { + this.pos++; + return Token.EQUALS_GREATERTHAN; + } + } + return Token.EQUALS; + + case CharCode.GREATERTHAN: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.GREATERTHAN) { + this.pos++; + if (this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.GREATERTHAN) { + this.pos++; + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS; + } + return Token.GREATERTHAN_GREATERTHAN_GREATERTHAN; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.GREATERTHAN_GREATERTHAN_EQUALS; + } + } + return Token.GREATERTHAN_GREATERTHAN; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.GREATERTHAN_EQUALS; + } + } + return Token.GREATERTHAN; + + case CharCode.QUESTION: + this.pos++; + return Token.QUESTION; + + case CharCode.OPENBRACKET: + this.pos++; + return Token.OPENBRACKET; + + case CharCode.CLOSEBRACKET: + this.pos++; + return Token.CLOSEBRACKET; + + case CharCode.CARET: + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.CARET_EQUALS; + } + return Token.CARET; + + case CharCode.OPENBRACE: + this.pos++; + return Token.OPENBRACE; + + case CharCode.BAR: + if (++this.pos < this.end) { + if (text.charCodeAt(this.pos) == CharCode.BAR) { + this.pos++; + return Token.BAR_BAR; + } + if (text.charCodeAt(this.pos) == CharCode.EQUALS) { + this.pos++; + return Token.BAR_EQUALS; + } + } + return Token.BAR; + + case CharCode.CLOSEBRACE: + this.pos++; + return Token.CLOSEBRACE; + + case CharCode.TILDE: + this.pos++; + return Token.TILDE; + + case CharCode.AT: + this.pos++; + return Token.AT; + + default: { + if (isIdentifierStart(c)) { + if (isKeywordCharacter(c)) { + const posBefore: i32 = this.pos; + while (++this.pos < this.end && isIdentifierPart(c = text.charCodeAt(this.pos))) { + if (!isKeywordCharacter(c)) { + this.pos = posBefore; + return Token.IDENTIFIER; + } + } + const keywordText: string = text.substring(posBefore, this.pos); + if (textToKeywordToken.has(keywordText) && !(preferIdentifier && possibleIdentifiers.has(keywordText))) + return textToKeywordToken.get(keywordText); + this.pos = posBefore; + } + return Token.IDENTIFIER; // expects a call to readIdentifier + } else if (isWhiteSpace(c)) { + this.pos++; + break; + } + this.error(DiagnosticCode.Invalid_character, this.range(this.pos, this.pos + 1)); + this.pos++; + return Token.INVALID; + } + } + } + } + + peek(checkOnNewLine: bool = false): Token { + const text: string = this.source.text; + if (this.nextToken < 0) { + const posBefore: i32 = this.pos; + const tokenBefore: Token = this.token; + const tokenPosBefore: i32 = this.tokenPos; + this.nextToken = this.unsafeNext(); + if (checkOnNewLine) { + this.nextTokenOnNewLine = false; + while (--this.tokenPos > posBefore) { + if (isLineBreak(text.charCodeAt(this.tokenPos))) { + this.nextTokenOnNewLine = true; + break; + } + } + } + this.pos = posBefore; + this.token = tokenBefore; + this.tokenPos = tokenPosBefore; + } + return this.nextToken; + } + + skip(token: Token): bool { + const posBefore: i32 = this.pos; + const tokenBefore: Token = this.token; + const tokenPosBefore: i32 = this.tokenPos; + if ((this.nextToken = this.unsafeNext(token == Token.IDENTIFIER)) == token) { + this.nextToken = -1; + return true; + } else { + this.pos = posBefore; + this.token = tokenBefore; + this.tokenPos = tokenPosBefore; + return false; + } + } + + mark(): void { + this.markedPos = this.pos; + this.markedToken = this.token; + this.markedTokenPos = this.tokenPos; + } + + reset(): void { + this.pos = this.markedPos; + this.token = this.markedToken; + this.tokenPos = this.markedTokenPos; + this.nextToken = -1; + } + + range(start: i32 = -1, end: i32 = -1): Range { + if (start < 0) { + start = this.tokenPos; + if (end < 0) + end = start; + } else if (end < 0) + end = this.pos; + return new Range(this.source, start, end); + } + + readIdentifier(): string { + const text: string = this.source.text; + const start: i32 = this.pos; + while (++this.pos < this.end && isIdentifierPart(text.charCodeAt(this.pos))); + return text.substring(start, this.pos); + } + + readString(): string { + const text: string = this.source.text; + const quote: i32 = text.charCodeAt(this.pos++); + let start: i32 = this.pos; + let result: string = ""; + while (true) { + if (this.pos >= this.end) { + result += text.substring(start, this.pos); + this.error(DiagnosticCode.Unterminated_string_literal, this.range(start - 1, this.end)); + break; + } + const c: i32 = text.charCodeAt(this.pos); + if (c == quote) { + result += text.substring(start, this.pos++); + break; + } + if (c == CharCode.BACKSLASH) { + result += text.substring(start, this.pos); + result += this.readEscapeSequence(); + start = this.pos; + continue; + } + if (isLineBreak(c)) { + result += text.substring(start, this.pos); + this.error(DiagnosticCode.Unterminated_string_literal, this.range(start - 1, this.pos)); + break; + } + this.pos++; + } + return result; + } + + readEscapeSequence(): string { + if (++this.pos >= this.end) { + this.error(DiagnosticCode.Unexpected_end_of_text, this.range(this.end)); + return ""; + } + + const text: string = this.source.text; + const c: i32 = text.charCodeAt(this.pos++); + switch (c) { + + case CharCode._0: + return "\0"; + + case CharCode.b: + return "\b"; + + case CharCode.t: + return "\t"; + + case CharCode.n: + return "\n"; + + case CharCode.v: + return "\v"; + + case CharCode.f: + return "\f"; + + case CharCode.r: + return "\r"; + + case CharCode.SINGLEQUOTE: + return "'"; + + case CharCode.DOUBLEQUOTE: + return "\""; + + case CharCode.u: { + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.OPENBRACE) { + this.pos++; + return this.readExtendedUnicodeEscape(); // \u{DDDDDDDD} + } + return this.readUnicodeEscape(); // \uDDDD + } + + case CharCode.CARRIAGERETURN: + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.LINEFEED) + this.pos++; + // fall through + + case CharCode.LINEFEED: + case CharCode.LINESEPARATOR: + case CharCode.PARAGRAPHSEPARATOR: + return ""; + default: + return String.fromCharCode(c); + } + } + + testRegexp(): bool { + // TODO: this'll require more context + return false; + } + + readRegexp(): string { + let result: string = ""; + let start: i32 = this.pos; + let escaped: bool = false; + const text: string = this.source.text; + while (true) { + if (this.pos >= this.end) { + result += text.substring(start, this.pos); + this.error(DiagnosticCode.Unterminated_regular_expression_literal, this.range(start, this.end)); + break; + } + if (text.charCodeAt(this.pos) == CharCode.BACKSLASH) { + this.pos++; + escaped = true; + continue; + } + const c: i32 = text.charCodeAt(this.pos); + if (c == CharCode.SLASH) { + result += text.substring(start, this.pos); + this.pos++; + break; + } + if (isLineBreak(c)) { + result += text.substring(start, this.pos); + this.error(DiagnosticCode.Unterminated_regular_expression_literal, this.range(start, this.pos)); + break; + } + this.pos++; + } + return result; + } + + testInteger(): bool { + const text: string = this.source.text; + if (text.charCodeAt(this.pos) == CharCode._0 && this.pos + 1 < this.end) { + switch (text.charCodeAt(this.pos + 2)) { + case CharCode.X: + case CharCode.x: + case CharCode.B: + case CharCode.b: + case CharCode.O: + case CharCode.o: + return true; + } + } + let pos: i32 = this.pos; + while (pos < this.end) { + const c: i32 = text.charCodeAt(pos); + if (c == CharCode.DOT || c == CharCode.E || c == CharCode.e) + return false; + if (c < CharCode._0 || c > CharCode._9) + break; + pos++; + } + return true; + } + + readInteger(): I64 { + const text: string = this.source.text; + if (text.charCodeAt(this.pos) == CharCode._0 && this.pos + 2 < this.end) { + switch (text.charCodeAt(this.pos + 1)) { + case CharCode.X: + case CharCode.x: + this.pos += 2; + return this.readHexInteger(); + case CharCode.B: + case CharCode.b: + this.pos += 2; + return this.readBinaryInteger(); + case CharCode.O: + case CharCode.o: + this.pos += 2; + return this.readOctalInteger(); + } + if (isOctalDigit(text.charCodeAt(this.pos + 1))) { + const start: i32 = this.pos; + this.pos++; + const value: I64 = this.readOctalInteger(); + this.error(DiagnosticCode.Octal_literals_are_not_allowed_in_strict_mode, this.range(start, this.pos)); + return value; + } + } + return this.readDecimalInteger(); + } + + readHexInteger(): I64 { + const text: string = this.source.text; + const start: i32 = this.pos; + let value: I64 = new I64(0, 0); + while (this.pos < this.end) { + const c: i32 = text.charCodeAt(this.pos); + if (c >= CharCode._0 && c <= CharCode._9) { + // value = value * 16 + c - CharCode._0; + value.mul32(16); + value.add32(c - CharCode._0); + } else if (c >= CharCode.A && c <= CharCode.F) { + // value = value * 16 + 10 + c - CharCode.A; + value.mul32(16); + value.add32(10 + c - CharCode.A); + } else if (c >= CharCode.a && c <= CharCode.f) { + // value = value * 16 + 10 + c - CharCode.a; + value.mul32(16); + value.add32(10 + c - CharCode.a); + } else + break; + this.pos++; + } + if (this.pos == start) + this.error(DiagnosticCode.Hexadecimal_digit_expected, this.range(start)); + return value; + } + + readDecimalInteger(): I64 { + const text: string = this.source.text; + const start: i32 = this.pos; + let value: I64 = new I64(0, 0); + while (this.pos < this.end) { + const c: i32 = text.charCodeAt(this.pos); + if (c >= CharCode._0 && c <= CharCode._9) { + // value = value * 10 + c - CharCode._0; + value.mul32(10); + value.add32(c - CharCode._0); + } else + break; + this.pos++; + } + if (this.pos == start) + this.error(DiagnosticCode.Digit_expected, this.range(start)); + return value; + } + + readOctalInteger(): I64 { + const text: string = this.source.text; + const start: i32 = this.pos; + let value: I64 = new I64(0, 0); + while (this.pos < this.end) { + const c: i32 = text.charCodeAt(this.pos); + if (c >= CharCode._0 && c <= CharCode._7) { + // value = value * 8 + c - CharCode._0; + value.mul32(8); + value.add32(c - CharCode._0); + } else + break; + this.pos++; + } + if (this.pos == start) + this.error(DiagnosticCode.Octal_digit_expected, this.range(start)); + return value; + } + + readBinaryInteger(): I64 { + const text: string = this.source.text; + const start: i32 = this.pos; + let value: I64 = new I64(); + while (this.pos < this.end) { + const c: i32 = text.charCodeAt(this.pos); + if (c == CharCode._0) { + // value = value * 2; + value.mul32(2); + } else if (c == CharCode._1) { + // value = value * 2 + 1; + value.mul32(2); + value.add32(1); + } + else + break; + this.pos++; + } + if (this.pos == start) + this.error(DiagnosticCode.Binary_digit_expected, this.range(start)); + return value; + } + + readFloat(): f64 { + let start: i32 = this.pos; + const text: string = this.source.text; + while (this.pos < this.end && isDecimalDigit(text.charCodeAt(this.pos))) + this.pos++; + if (this.pos < this.end && text.charCodeAt(this.pos) == CharCode.DOT) { + this.pos++; + while (this.pos < this.end && isDecimalDigit(text.charCodeAt(this.pos))) + this.pos++; + } + if (this.pos < this.end) { + const c: i32 = text.charCodeAt(this.pos); + if (c == CharCode.E || c == CharCode.e) { + if (++this.pos < this.end && text.charCodeAt(this.pos) == CharCode.MINUS) + this.pos++; + while (this.pos < this.end && isDecimalDigit(text.charCodeAt(this.pos))) + this.pos++; + } + } + return parseFloat(text.substring(start, this.pos)); + } + + readUnicodeEscape(): string { + let remain: i32 = 4; + let value: i32 = 0; + const text: string = this.source.text; + while (this.pos < this.end) { + const c: i32 = text.charCodeAt(this.pos++); + if (c >= CharCode._0 && c <= CharCode._9) + value = value * 16 + c - CharCode._0; + else if (c >= CharCode.A && c <= CharCode.F) + value = value * 16 + 10 + c - CharCode.A; + else if (c >= CharCode.a && c <= CharCode.f) + value = value * 16 + 10 + c - CharCode.a; + else { + this.error(DiagnosticCode.Hexadecimal_digit_expected, this.range(this.pos - 1, this.pos)); + return ""; + } + if (--remain == 0) + break; + } + if (remain) { + this.error(DiagnosticCode.Unexpected_end_of_text, this.range(this.pos)); + return ""; + } + return String.fromCharCode(value); + } + + private readExtendedUnicodeEscape(): string { + const start: i32 = this.pos; + const value: I64 = this.readHexInteger(); + let invalid: bool = false; + + if (value.gt32(0x10FFFF)) { + this.error(DiagnosticCode.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive, this.range(start, this.pos)); + invalid = true; + } + + const value32: i32 = value.toI32(); + const text: string = this.source.text; + if (this.pos >= this.end) { + this.error(DiagnosticCode.Unexpected_end_of_text, this.range(start, this.end)); + invalid = true; + } else if (text.charCodeAt(this.pos) == CharCode.CLOSEBRACE) { + this.pos++; + } else { + this.error(DiagnosticCode.Unterminated_Unicode_escape_sequence, this.range(start, this.pos)); + invalid = true; + } + + if (invalid) + return ""; + return value32 < 65536 + ? String.fromCharCode(value32) + : String.fromCharCode((((value32 - 65536) / 1024 | 0) + 0xD800) as i32, ((value32 - 65536) % 1024 + 0xDC00) as i32); + } +} + +function isWhiteSpace(c: i32): bool { + return c == CharCode.SPACE + || c == CharCode.TAB + || c == CharCode.VERTICALTAB + || c == CharCode.FORMFEED + || c == CharCode.NONBREAKINGSPACE + || c == CharCode.NEXTLINE + || c == CharCode.OGHAM + || c >= CharCode.ENQUAD && c <= CharCode.ZEROWIDTHSPACE + || c == CharCode.NARRINOBREAKSPACE + || c == CharCode.MATHEMATICALSPACE + || c == CharCode.IDEOGRAPHICSPACE + || c == CharCode.BYTEORDERMARK; +} + +function isDecimalDigit(c: i32): bool { + return c >= CharCode._0 && c <= CharCode._9; +} + +function isOctalDigit(c: i32): bool { + return c >= CharCode._0 && c <= CharCode._7; +} + +function isIdentifierStart(c: i32): bool { + return c >= CharCode.A && c <= CharCode.Z + || c >= CharCode.a && c <= CharCode.z + || c == CharCode.$ + || c == CharCode._ + || c > 0x7f && isUnicodeIdentifierStart(c); +} + +function isKeywordCharacter(c: i32): bool { + return c >= CharCode.a && c <= CharCode.z; +} + +function isIdentifierPart(c: i32): bool { + return c >= CharCode.A && c <= CharCode.Z + || c >= CharCode.a && c <= CharCode.z + || c >= CharCode._0 && c <= CharCode._9 + || c == CharCode.$ + || c == CharCode._ + || c > 0x7f && isUnicodeIdentifierPart(c); +} + +// storing as u16 to save memory +const unicodeIdentifierStart: u16[] = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +const unicodeIdentifierPart: u16[] = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; + +function lookupInUnicodeMap(code: u16, map: u16[]): bool { + if (code < map[0]) + return false; + + let lo: i32 = 0; + let hi: i32 = map.length; + let mid: i32; + + while (lo + 1 < hi) { + mid = lo + (hi - lo) / 2; + mid -= mid % 2; + if (map[mid] <= code && code <= map[mid + 1]) + return true; + if (code < map[mid]) + hi = mid; + else + lo = mid + 2; + } + return false; +} + +function isUnicodeIdentifierStart(code: i32): bool { + if (code < 0 || code > 0xffff) return false; + return lookupInUnicodeMap(code as u16, unicodeIdentifierStart); +} + +function isUnicodeIdentifierPart(code: i32): bool { + if (code < 0 || code > 0xffff) return false; + return lookupInUnicodeMap(code as u16, unicodeIdentifierPart); +} diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 00000000..33989936 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": [ + "dom", + "es6" + ], + "types": [ + "node" + ], + "strictNullChecks": true, + "alwaysStrict": true, + "outDir": "../out" + }, + "files": [ + "ast.ts", + "binaryen.d.ts", + "binaryen.ts", + "compiler.ts", + "diagnosticMessages.generated.ts", + "diagnostics.ts", + "glue/js.d.ts", + "glue/js.ts", + "index.ts", + "parser.ts", + "program.ts", + "reflection.ts", + "tokenizer.ts", + "util.ts", + "util/i64.ts" + ] +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 00000000..ceb3efa9 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,228 @@ +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 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); +} diff --git a/src/util/i64.ts b/src/util/i64.ts new file mode 100644 index 00000000..f8a31509 --- /dev/null +++ b/src/util/i64.ts @@ -0,0 +1,520 @@ +/* + + To remain compatible with TSC / compiling to JS, we have to emulate I64s in a + portable way. The following is based on long.js with the main difference being + that instances are mutable and operations affect 'this'. In our scenario, + that's useful because it's mostly used for constant evaluation and we are + exclusively interested in the result (saves a heap of allocations). + +*/ + +// TODO: div/mod + +const I64_MIN_LO: i32 = 0; +const I64_MIN_HI: i32 = 0x80000000 | 0; + +export class I64 { + + lo: i32; + hi: i32; + + static fromI32(n: i32): I64 { + return new I64(n, n < 0 ? -1 : 0); + } + + constructor(lo: i32 = 0, hi: i32 = 0) { + this.lo = lo; + this.hi = hi; + } + + get isZero(): bool { + return this.lo == 0 && this.hi == 0; + } + + get isPositive(): bool { + return this.hi >= 0; + } + + get isNegative(): bool { + return this.hi < 0; + } + + get isOdd(): bool { + return (this.lo & 1) == 1; + } + + get isEven(): bool { + return (this.lo & 1) == 0; + } + + toI32(): i32 { + return this.lo; + } + + eq(other: I64): bool { + return this.eq32(other.lo, other.hi); + } + + eq32(lo: i32, hi: i32 = 0): bool { + return this.lo == lo && this.hi == hi; + } + + ne(other: I64): bool { + return this.ne32(other.lo, other.hi); + } + + ne32(lo: i32, hi: i32 = 0): bool { + return this.lo != lo || this.hi != hi; + } + + neg(): void { + this.lo = ~this.lo; + this.hi = ~this.hi; + this.add32(1, 0); + } + + add(other: I64): void { + this.add32(other.lo, other.hi); + } + + add32(lo: i32, hi: i32 = 0): void { + i64_add_internal(this.lo, this.hi, lo, hi); + this.lo = i64_lo; + this.hi = i64_hi; + } + + sub(other: I64): void { + this.sub32(other.lo, other.hi); + } + + sub32(lo: i32, hi: i32 = 0): void { + i64_add_internal(~lo, ~hi, 1, 0); + this.add32(i64_lo, i64_hi); + } + + comp(other: I64): i32 { + return this.comp32(other.lo, other.hi); + } + + comp32(lo: i32, hi: i32 = 0): i32 { + if (this.lo == lo && this.hi == hi) + return 0; + if (this.hi < 0 && hi >= 0) + return -1; + if (this.hi >= 0 && hi < 0) + return 1; + i64_add_internal(~lo, ~hi, 1, 0); + i64_add_internal(this.lo, this.hi, i64_lo, i64_hi); + return i64_hi < 0 ? -1 : 1; + } + + lt(other: I64): bool { + return this.lt32(other.lo, other.hi); + } + + lt32(lo: i32, hi: i32 = 0): bool { + return this.comp32(lo, hi) < 0; + } + + lte(other: I64): bool { + return this.lte32(other.lo, other.hi); + } + + lte32(lo: i32, hi: i32 = 0): bool { + return this.comp32(lo, hi) <= 0; + } + + gt(other: I64): bool { + return this.gt32(other.lo, other.hi); + } + + gt32(lo: i32, hi: i32 = 0): bool { + return this.comp32(lo, hi) > 0; + } + + gte(other: I64): bool { + return this.gte32(other.lo, other.hi); + } + + gte32(lo: i32, hi: i32 = 0): bool { + return this.comp32(lo, hi) >= 0; + } + + mul(other: I64): void { + this.mul32(other.lo, other.hi); + } + + mul32(lo: i32, hi: i32 = 0): void { + if (this.lo == 0 && this.hi == 0) + return; + + if (lo == 0 && hi == 0) { + this.lo = 0; + this.hi = 0; + return; + } + + // this == MIN + if (this.lo == I64_MIN_LO && this.hi == I64_MIN_HI) { + this.lo = 0; // == MIN_LO + this.hi = lo & 1 ? I64_MIN_HI : 0; // other.isOdd ? this = MIN : this = ZERO + return; + } + + // other == MIN + if (lo == I64_MIN_LO && hi == I64_MIN_HI) { + this.hi = this.lo & 1 ? I64_MIN_HI : 0; // this.isOdd ? this = MIN : this = ZERO + this.lo = 0; // == MIN_LO + return; + } + + if (this.hi < 0) { + this.neg(); + + // both negative: negate both and multiply + if (hi < 0) { + i64_add_internal(~lo, ~hi, 1, 0); + i64_mul_internal(this.lo, this.hi, i64_lo, i64_hi); + this.lo = i64_lo; + this.hi = i64_hi; + + // this negative: negate this, multiply and negate result + } else { + i64_mul_internal(this.lo, this.hi, lo, hi); + this.lo = i64_lo; + this.hi = i64_hi; + this.neg(); + } + return; + + // other negative: negate other, multiply and negate result + } else if (hi < 0) { + i64_add_internal(~lo, ~hi, 1, 0); + i64_mul_internal(this.lo, this.hi, i64_lo, i64_hi); + this.lo = i64_lo; + this.hi = i64_hi; + this.neg(); + return; + } + + // both positive + i64_mul_internal(this.lo, this.hi, lo, hi); + this.lo = i64_lo; + this.hi = i64_hi; + } + + div(other: I64): void { + this.div32(other.lo, other.hi); + } + + div32(lo: i32, hi: i32 = 0): void { + // other == 0 + if (lo == 0 && hi == 0) + throw new Error("division by zero"); + + // this == 0 + if (this.lo == 0 && this.hi == 0) + return; + + // this == MIN + if (this.lo == I64_MIN_LO && this.hi == I64_MIN_HI) { + + // other == 1 or -1 + if (lo == 1 && hi == 0 || lo == -1 && hi == -1) // -MIN == MIN + return; + + // both == MIN + if (lo == I64_MIN_LO && hi == I64_MIN_HI) { + this.lo = 1; + this.hi = 0; + return; + } + + // |other| >= 2, so |this/other| < |MIN_VALUE| + let tempLo: i32 = this.lo; + let tempHi: i32 = this.hi; + this.shr32(1, 0); + this.div32(lo, hi); + this.shl32(1, 0); + if (this.lo == 0 && this.hi == 0) { + if (hi < 0) { + this.lo = 1; + this.hi = 0; + } else { + this.lo = -1; + this.hi = -1; + } + return; + } + i64_mul_internal(lo, hi, this.lo, this.hi); + this.lo = tempLo; + this.hi = tempHi; + tempLo = i64_lo; + tempHi = i64_hi; + this.div32(lo, hi); + this.sub32(i64_lo, i64_hi); + i64_add_internal(tempLo, tempHi, this.lo, this.hi); + this.lo = i64_lo; + this.hi = i64_hi; + return; + } + + if (this.hi < 0) { + this.neg(); + + // both negative: negate both and divide + if (hi < 0) { + i64_add_internal(~lo, ~hi, 1, 0); + i64_div_internal(this.lo, this.hi, i64_lo, i64_hi); + this.lo = i64_lo; + this.hi = i64_hi; + + // this negative: negate this, divide and negate result + } else { + i64_div_internal(this.lo, this.hi, lo, hi); + this.lo = i64_lo; + this.hi = i64_hi; + this.neg(); + } + return; + + // other negative: negate other, divide and negate result + } else if (hi < 0) { + i64_add_internal(~lo, ~hi, 1, 0); + i64_div_internal(this.lo, this.hi, i64_lo, i64_hi); + this.lo = i64_lo; + this.hi = i64_hi; + this.neg(); + return; + } + + // both positive + i64_div_internal(this.lo, this.hi, lo, hi); + this.lo = i64_lo; + this.hi = i64_hi; + } + + mod(other: I64): void { + this.mod32(other.lo, other.hi); + } + + mod32(lo: i32, hi: i32 = 0): void { + const thisLo: i32 = this.lo; + const thisHi: i32 = this.hi; + this.div32(lo, hi); + this.mul32(lo, hi); + const resLo: i32 = this.lo; + const resHi: i32 = this.hi; + this.lo = thisLo; + this.hi = thisHi; + this.sub32(resLo, resHi); + } + + not(): void { + this.lo = ~this.lo; + this.hi = ~this.hi; + } + + and(other: I64): void { + this.and32(other.lo, other.hi); + } + + and32(lo: i32, hi: i32 = 0): void { + this.lo &= lo; + this.hi &= hi; + } + + or(other: I64): void { + this.or32(other.lo, other.hi); + } + + or32(lo: i32, hi: i32 = 0): void { + this.lo |= lo; + this.hi |= hi; + } + + xor(other: I64): void { + this.xor32(other.lo, other.hi); + } + + xor32(lo: i32, hi: i32 = 0): void { + this.lo ^= lo; + this.hi ^= hi; + } + + shl(other: I64): void { + this.shl32(other.lo, other.hi); + } + + shl32(lo: i32, hi: i32 = 0): void { + if ((lo &= 63) == 0) + return; + if (lo < 32) { + this.hi = (this.hi << lo) | (this.lo >>> (32 - lo)); + this.lo = this.lo << lo; + } else { + this.hi = this.lo << (lo - 32); + this.lo = 0; + } + } + + shr(other: I64): void { + this.shr32(other.lo, other.hi); + } + + shr32(lo: i32, hi: i32 = 0): void { + if ((lo &= 63) == 0) + return; + if (lo < 32) { + this.lo = (this.lo >>> lo) | (this.hi << (32 - lo)); + this.hi = this.hi >> lo; + } else { + this.lo = this.hi >> (lo - 32); + this.hi = this.hi >= 0 ? 0 : -1; + } + } + + shru(other: I64): void { + this.shru32(other.lo, other.hi); + } + + shru32(lo: i32, hi: i32 = 0): void { + if ((lo &= 63) == 0) + return; + if (lo < 32) { + this.lo = (this.lo >>> lo) | (this.hi << (32 - lo)); + this.hi = (this.hi >>> lo) | 0; + } else if (lo == 32) { + this.lo = this.hi; + this.hi = 0; + } else { + this.lo = (this.hi >>> (lo - 32)) | 0; + this.hi = 0; + } + } + + clone(): I64 { + return new I64(this.lo, this.hi); + } + + toString(): string { + let negative: bool = false; + if (this.hi < 0) { + i64_add_internal(~this.lo, ~this.hi, 1, 0); + negative = true; + } else { + i64_lo = this.lo; + i64_hi = this.hi; + } + + if (i64_hi) { + let lo: string = (i64_lo as u32 >>> 0).toString(16); + while (lo.length < 8) + lo = "0" + lo; + return (negative ? "-0x" : "0x") + (i64_hi as u32 >>> 0).toString(16) + lo; + } + return negative ? "-" + i64_lo.toString() : i64_lo.toString(); + } +} + +let i64_lo: i32 = 0; +let i64_hi: i32 = 0; + +function i64_add_internal(lo: i32, hi: i32, otherLo: i32, otherHi: i32): void { + let a48: i32 = hi >>> 16; + let a32: i32 = hi & 0xFFFF; + let a16: i32 = lo >>> 16; + let a00: i32 = lo & 0xFFFF; + + let b48: i32 = otherHi >>> 16; + let b32: i32 = otherHi & 0xFFFF; + let b16: i32 = otherLo >>> 16; + let b00: i32 = otherLo & 0xFFFF; + + let c48: i32 = 0, c32: i32 = 0, c16: i32 = 0, c00: i32 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + + i64_lo = (c16 << 16) | c00; + i64_hi = (c48 << 16) | c32; +} + +function i64_mul_internal(lo: i32, hi: i32, otherLo: i32, otherHi: i32): void { + let a48: i32 = hi >>> 16; + let a32: i32 = hi & 0xFFFF; + let a16: i32 = lo >>> 16; + let a00: i32 = lo & 0xFFFF; + + let b48: i32 = otherHi >>> 16; + let b32: i32 = otherHi & 0xFFFF; + let b16: i32 = otherLo >>> 16; + let b00: i32 = otherLo & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + + i64_lo = (c16 << 16) | c00; + i64_hi = (c48 << 16) | c32; +} + +function i64_div_internal(lo: i32, hi: i32, otherLo: i32, otherHi: i32): void { + throw new Error("not implemented"); +} + +export class U64 extends I64 { + + static fromI32(n: i32): U64 { + return new U64(n, 0); + } + + get isPositive(): bool { + return true; + } + + get isNegative(): bool { + return false; + } + + comp32(lo: i32, hi: i32): i32 { + // uses both a cast and a js-like shift for portability + return ((hi as u32 >>> 0) > (this.hi as u32 >>> 0)) || (hi == this.hi && (lo as u32 >>> 0) > (this.lo as u32 >>> 0)) ? -1 : 1; + } + + neg(): void { + this.lo = ~this.lo; + this.hi = ~this.hi; + this.add32(1, 0); + } + + clone(): U64 { + return new U64(this.lo, this.hi); + } +} diff --git a/tests/binaryen.ts b/tests/binaryen.ts new file mode 100644 index 00000000..a0e69e09 --- /dev/null +++ b/tests/binaryen.ts @@ -0,0 +1,56 @@ +/// +import "../src/glue/js"; +import { Type, Module, MemorySegment, BinaryOp } from "../src/binaryen"; +import { Target } from "../src/compiler"; +import { U64 } from "../src/util"; + +const mod = Module.create(); + +mod.setMemory(1, Module.MAX_MEMORY_WASM32, [ + MemorySegment.create(new Uint8Array(4), U64.fromI32(8)), + MemorySegment.create(new Uint8Array(4), U64.fromI32(12)) +], Target.WASM32, "memory"); + +const add = mod.addFunctionType("iii", Type.I32, [ Type.I32, Type.I32 ]); +mod.addFunction("add", add, [], mod.createReturn( + mod.createBinary(BinaryOp.AddI32, + mod.createGetLocal(0, Type.I32), + mod.createGetLocal(1, Type.I32) + ) +)); +mod.addExport("add", "add"); + +const lit = mod.addFunctionType("I", Type.I64, []); +mod.addFunction("lit", lit, [], mod.createReturn( + mod.createI64(0, 0x80000000) // I64_MIN +)); +mod.addExport("lit", "lit"); + +mod.addGlobal("42", Type.I32, false, mod.createI32(42)); + +const aSwitch = mod.addFunctionType("i", Type.I32, []); +mod.addFunction("aSwitch", aSwitch, [], mod.createBlock("", [ + mod.createBlock("a", [ + mod.createBlock("b", [ + mod.createBlock("c", [ + mod.createBlock("d", [ + mod.createSwitch( + ["a", "b", "c"], + "d", + mod.createI32(4) + ) + ]), + mod.createReturn(mod.createI32(3)) + ]), + mod.createReturn(mod.createI32(2)) + ]), + mod.createReturn(mod.createI32(1)) + ]), + mod.createReturn(mod.createI32(0)) +])); +mod.addExport("aSwitch", "aSwitch"); + +// mod.optimize(); +if (mod.validate()) + _BinaryenModulePrint(mod.ref); +_BinaryenModuleDispose(mod.ref); diff --git a/tests/i64.ts b/tests/i64.ts new file mode 100644 index 00000000..b6a76832 --- /dev/null +++ b/tests/i64.ts @@ -0,0 +1,37 @@ +import { I64 } from "../src/util/i64"; +import * as Long from "long"; +import * as assert from "assert"; + +function test(fn, lo, hi, otherLo, otherHi) { + let expected = Long.fromBits(lo, hi)[fn](Long.fromBits(otherLo, otherHi)); + let actual = new I64(lo, hi); actual[fn + "32"](otherLo, otherHi); + assert.equal(actual.lo, expected.low, fn + " lo "); + assert.equal(actual.hi, expected.high, fn + " hi"); +} + +function rand() { + let r = Math.random(); + // 10% edge cases + if (r < 0.05) return 0x80000000 | 0; + else if (r < 0.1) return 0; + return (Math.random() * 0xffffffff) | 0; +} + +let i = 0; +while (i++ < 1000000) { + let lo = rand(); + let hi = rand(); + let otherLo = rand(); + let otherHi = rand(); + // console.log(lo, hi, otherLo, otherHi); + test("add", lo, hi, otherLo, otherHi); + test("sub", lo, hi, otherLo, otherHi); + test("mul", lo, hi, otherLo, otherHi); + test("shl", lo, hi, otherLo, otherHi); + test("shr", lo, hi, otherLo, otherHi); + test("shru", lo, hi, otherLo, otherHi); + test("and", lo, hi, otherLo, otherHi); + test("or", lo, hi, otherLo, otherHi); + test("xor", lo, hi, otherLo, otherHi); +} +console.log("done"); diff --git a/tests/parser/fixtures/class.ts b/tests/parser/fixtures/class.ts new file mode 100644 index 00000000..82159670 --- /dev/null +++ b/tests/parser/fixtures/class.ts @@ -0,0 +1,12 @@ +export class Test { + instanceFunction(): void { + } + static staticFunction(): void { + } + get instanceGetter(): i32 { + } + static set staticSetter(v: i32): i32 { + } + instanceField: i32; + static staticField: i32; +} diff --git a/tests/parser/fixtures/enum.ts b/tests/parser/fixtures/enum.ts new file mode 100644 index 00000000..962cd7c6 --- /dev/null +++ b/tests/parser/fixtures/enum.ts @@ -0,0 +1,10 @@ +export const enum A { + B = 1, + C, + D = 3 +} +enum E { + F, + G = 1 + 2, + H = 3 * 4 +} diff --git a/tests/parser/fixtures/function.ts b/tests/parser/fixtures/function.ts new file mode 100644 index 00000000..3ac855de --- /dev/null +++ b/tests/parser/fixtures/function.ts @@ -0,0 +1,4 @@ +function simple(): void { +} +function typeparams(a: V | null = null): void { +} diff --git a/tests/parser/fixtures/precedence.tree.ts b/tests/parser/fixtures/precedence.tree.ts new file mode 100644 index 00000000..805d32d0 --- /dev/null +++ b/tests/parser/fixtures/precedence.tree.ts @@ -0,0 +1,171 @@ +// incrementing precedence + a += + b + ? + X + : + c + || + d + && + e + | + f + ^ + g + & + h + == + i + < + j + << + k + + + l + / + ++ + m + ++ +; +// decrementing precedence + ++ + a + ++ + / + b + + + c + << + d + < + e + == + f + & + g + ^ + h + | + i + && + j + || + k +? + X +: + y + = + l +; +// assignment (right-to-left) + a += + b + += + c + -= + d + **= + e + *= + f + /= + g + %= + h + <<= + i + >>= + j + >>>= + k + &= + l + ^= + m + |= + n +; +// equality (left-to-right) + a + == + b + != + c + === + d +!== + e +; +// relational (left-to-right) + a + < + b + <= + c + > + d +>= + e +; +// bitwise shift (left-to-right) + a + << + b + >> + c +>>> + d +; +// addition (left-to-right) + a + + + b +- + c +; +// exponentiation (right-to-left), multiplication (left-to-right) + a + ** + b + * + c + / + d + % + e + ** + f +* + g +; +// prefix (right-to-left) +delete + void + typeof + -- + ++ + - + + + ~ + ! + a +; +// parenthesized + ( + a + + + ( + b + / + c + ) + - + d + ) +* + e +; diff --git a/tests/parser/index.ts b/tests/parser/index.ts new file mode 100644 index 00000000..80878940 --- /dev/null +++ b/tests/parser/index.ts @@ -0,0 +1,46 @@ +import * as fs from "fs"; +import * as diff from "diff"; +import * as chalk from "chalk"; + +import "../../src/glue/js"; +import { NodeKind, ExpressionStatement } from "../../src/ast"; +import { Parser } from "../../src/parser"; + +const files = fs.readdirSync(__dirname + "/fixtures"); +files.forEach(filename => { + if (filename.charAt(0) == "_") return; + const isTree = filename.indexOf(".tree.") >= 0; + const parser = new Parser(); + const text = fs.readFileSync(__dirname + "/fixtures/" + filename, { encoding: "utf8" }).replace(/\r?\n/g, "\n").replace(/^\/\/.*\r?\n/mg, ""); + parser.parseFile(text, filename, true); + var sb: string[] = []; + if (isTree) { + const statements = parser.program.sources[0].statements; + statements.forEach(stmt => { + if (stmt.kind != NodeKind.EXPRESSION) return; + (stmt).expression.serializeAsTree(sb, 0); + sb.push(";\n"); + }); + } else + parser.program.sources[0].serialize(sb); + const actual = sb.join(""); + const expected = isTree ? text : text.replace(/^\s+/mg, ""); + const diffs = diff.diffLines(expected, actual); + let changed = false; + diffs.forEach(part => { + if (part.added || part.removed) + changed = true; + }); + if (changed) { // print it + console.log("Differences in " + filename + ":"); + diffs.forEach(part => { + if (part.added || part.removed) + changed = true; + process.stderr.write((part.added ? chalk.green : part.removed ? chalk.red : chalk.grey)(part.value)); + }); + } else { + console.log("No differences in " + filename + "."); + } + // parser.program.initialize(); + // console.log(parser.program.names); +}); diff --git a/tests/path.ts b/tests/path.ts new file mode 100644 index 00000000..d0c04505 --- /dev/null +++ b/tests/path.ts @@ -0,0 +1,8 @@ +import { normalizePath, resolvePath } from "../src/util"; +import * as path from "path"; + +let test = "./Y/./N/./N/../../././../Y/./."; +console.log(normalizePath(test)); +console.log(path.posix.normalize(test)); + +console.log(resolvePath("../../..", "lib/util/i64.ts")); diff --git a/tests/tokenizer.ts b/tests/tokenizer.ts new file mode 100644 index 00000000..32fa7f8a --- /dev/null +++ b/tests/tokenizer.ts @@ -0,0 +1,23 @@ +import "../src/glue/js"; +import { Tokenizer, Token } from "../src/tokenizer"; +import { Source } from "../src/reflection"; +import * as fs from "fs"; + +const text = fs.readFileSync(__dirname + "/../src/tokenizer.ts").toString(); + +const tn = new Tokenizer(new Source("tokenizer.ts", text)); + +let token; +do { + token = tn.next(); + let range = tn.range(); + console.log(Token[token] + " -> " + range.source.text.substring(range.start, range.end)); + if (token == Token.IDENTIFIER) + console.log("> " + tn.readIdentifier()); + else if (token == Token.INTEGERLITERAL) + console.log("> " + tn.readInteger()); + else if (token == Token.FLOATLITERAL) + console.log("> " + tn.readFloat()); + else if (token == Token.STRINGLITERAL) + console.log("> " + tn.readString()); +} while (token != Token.ENDOFFILE);