diff --git a/src/compiler.ts b/src/compiler.ts index 64af551a..49c2c134 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -257,8 +257,8 @@ export class Compiler extends DiagnosticEmitter { break; case NodeKind.EXPORT: - if ((statement).path) - this.compileSourceByPath(((statement).path).value, (statement).path); + if ((statement).normalizedPath != null) + this.compileSourceByPath((statement).normalizedPath, (statement).path); if (noTreeShaking || isEntry) this.compileExportStatement(statement); break; @@ -517,10 +517,10 @@ export class Compiler extends DiagnosticEmitter { const internalPath: string | null = statement.path ? statement.internalPath : statement.range.source.internalPath; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { const member: ExportMember = members[i]; - const internalExportName: string = internalPath + PATH_DELIMITER + member.identifier.name; + const internalExportName: string = internalPath + PATH_DELIMITER + member.externalIdentifier.name; const element: Element | null = this.program.exports.get(internalExportName); - if (!element) - throw new Error("unexpected missing element"); + if (!element) // reported in Program#initialize + continue; switch (element.kind) { case ElementKind.CLASS_PROTOTYPE: @@ -1356,6 +1356,7 @@ export class Compiler extends DiagnosticEmitter { // TODO: sizeof, load, store, see program.ts/initializeBuiltins } } else { + // TODO: infer type arguments from parameter types if omitted functionInstance = (element).resolveInclTypeArguments(expression.typeArguments, this.currentFunction.contextualTypeArguments, expression); // reports } if (!functionInstance) diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 73f75f43..ecf9a90c 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -48,6 +48,7 @@ export enum DiagnosticCode { _abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration = 1242, Duplicate_identifier_0 = 2300, Cannot_find_name_0 = 2304, + Module_0_has_no_exported_member_1 = 2305, Generic_type_0_requires_1_type_argument_s = 2314, Type_0_is_not_generic = 2315, Type_0_is_not_assignable_to_type_1 = 2322, @@ -115,6 +116,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { 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 2305: return "Module '{0}' has no exported member '{1}'."; 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}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index eef00a2e..ec3a598d 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -48,6 +48,7 @@ "Duplicate identifier '{0}'.": 2300, "Cannot find name '{0}'.": 2304, + "Module '{0}' has no exported member '{1}'.": 2305, "Generic type '{0}' requires {1} type argument(s).": 2314, "Type '{0}' is not generic.": 2315, "Type '{0}' is not assignable to type '{1}'.": 2322, diff --git a/src/diagnostics.ts b/src/diagnostics.ts index db1a5710..aa0f8630 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -154,8 +154,8 @@ export abstract class DiagnosticEmitter { 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 - console.log(new Error().stack); + console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary + // console.log(new Error().stack); } error(code: DiagnosticCode, range: Range, arg0: string | null = null, arg1: string | null = null): void { diff --git a/src/module.ts b/src/module.ts index d1d007a1..39acbdf2 100644 --- a/src/module.ts +++ b/src/module.ts @@ -613,6 +613,14 @@ export class Module { return _BinaryenModuleValidate(this.ref) == 1; } + toBinary(): Uint8Array { + throw new Error("not implemented"); + } + + toText(): string { + throw new Error("not implemented"); + } + dispose(): void { if (!this.ref) return; // sic _BinaryenModuleDispose(this.ref); diff --git a/src/program.ts b/src/program.ts index 111eff03..0f75e378 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,6 +1,6 @@ import { Target } from "./compiler"; import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER } from "./constants"; -import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; +import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, DiagnosticCategory } from "./diagnostics"; import { Type, typesToString } from "./types"; import { I64 } from "./util"; import { @@ -40,19 +40,20 @@ import { VariableDeclaration, VariableStatement, - hasModifier + hasModifier, + mangleInternalName } from "./ast"; class QueuedExport { - isForeign: bool; + isReExport: bool; referencedName: string; member: ExportMember; } class QueuedImport { internalName: string; - importName: string; + referencedName: string; declaration: ImportDeclaration; } @@ -130,61 +131,63 @@ export class Program extends DiagnosticEmitter { } } - // at this point queued exports should be resolvable - for (let [exportName, queuedExport] of queuedExports) { // all file-level exports - if (queuedExport.isForeign) { - const seen: Set = new Set(); - while (queuedExports.has(queuedExport.referencedName)) { - queuedExport = queuedExports.get(queuedExport.referencedName); - if (seen.has(queuedExport)) - break; - seen.add(queuedExport); - } - if (this.exports.has(queuedExport.referencedName)) { - const element: Element = this.exports.get(queuedExport.referencedName); - if (!this.exports.has(exportName)) - this.exports.set(exportName, element); - if (queuedExport.member.range.source.isEntry) - element.globalExportName = queuedExport.member.externalIdentifier.name; - } else - this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.referencedName); - } else /* local */ { - if (this.elements.has(queuedExport.referencedName)) { - const element: Element = this.elements.get(queuedExport.referencedName); - if (!this.exports.has(exportName)) - this.exports.set(exportName, element); - if (queuedExport.member.range.source.isEntry) - element.globalExportName = queuedExport.member.externalIdentifier.name; - } else - this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.referencedName); + let element: Element | null; + + // queued imports should be resolvable now + for (let i: i32 = 0; i < queuedImports.length;) { + const queuedImport: QueuedImport = queuedImports[i]; + element = this.tryResolveImport(queuedImport.referencedName, queuedExports); + if (element) { + this.elements.set(queuedImport.internalName, element); + queuedImports.splice(i, 1); + } else { + this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedImport.declaration.range, (queuedImport.declaration.parent).path.value, queuedImport.declaration.externalIdentifier.name); + ++i; } } - // at this point queued imports should be resolvable as well - for (let i: i32 = 0, k: i32 = queuedImports.length; i < k; ++i) { - const queuedImport: QueuedImport = queuedImports[i]; - const internalName: string = queuedImport.internalName; - const seen: Set = new Set(); - let importName: string = queuedImport.importName; - while (queuedExports.has(importName)) { - const queuedExport: QueuedExport = queuedExports.get(importName); - importName = queuedExport.referencedName; - if (seen.has(queuedExport)) + // queued exports should be resolvable noww + for (let [exportName, queuedExport] of queuedExports) { + let currentExport: QueuedExport | null = queuedExport; + do { + if (currentExport.isReExport) { + element = this.exports.get(currentExport.referencedName); + if (element) { + this.exports.set(exportName, element); + break; + } + currentExport = queuedExports.get(currentExport.referencedName); + if (!currentExport) + this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.member.externalIdentifier.range, ((queuedExport.member.parent).path).value, queuedExport.member.externalIdentifier.name); + } else { + element = this.elements.get(currentExport.referencedName); + if (element) + this.exports.set(exportName, element); + else + this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.range, queuedExport.member.identifier.name); break; - seen.add(queuedExport); - } - if (this.exports.has(importName)) { - if (this.elements.has(internalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, queuedImport.declaration.identifier.range, internalName); - else { - const element: Element = this.exports.get(importName); - this.elements.set(internalName, element); } - } else - this.error(DiagnosticCode.Cannot_find_name_0, queuedImport.declaration.externalIdentifier.range, importName); + } while (currentExport); } } + private tryResolveImport(referencedName: string, queuedExports: Map): Element | null { + let element: Element | null; + do { + element = this.exports.get(referencedName); + if (element) + return element; + const queuedExport: QueuedExport | null = queuedExports.get(referencedName); + if (!queuedExport) + return null; + if (queuedExport.isReExport) { + referencedName = queuedExport.referencedName; + continue; + } + return this.elements.get(queuedExport.referencedName); + } while (true); + } + private initializeClass(declaration: ClassDeclaration): void { const internalName: string = declaration.internalName; if (this.elements.has(internalName)) { @@ -288,21 +291,79 @@ export class Program extends DiagnosticEmitter { } private initializeExport(member: ExportMember, internalPath: string | null, queuedExports: Map): void { - const exportName: string = member.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name; - if (queuedExports.has(exportName)) { - this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, exportName); + const externalName: string = member.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name; + + if (this.exports.has(externalName)) { + this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName); return; } - const queuedExport: QueuedExport = new QueuedExport(); + + let referencedName: string; + + // export local element if (internalPath == null) { - queuedExport.isForeign = false; - queuedExport.referencedName = member.range.source.internalPath + PATH_DELIMITER + member.identifier.name; + referencedName = member.range.source.internalPath + PATH_DELIMITER + member.identifier.name; + + // resolve right away if the element exists + if (this.elements.has(referencedName)) { + this.exports.set(externalName, this.elements.get(referencedName)); + return; + } + + // otherwise queue it + if (queuedExports.has(externalName)) { + this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName); + return; + } + const queuedExport: QueuedExport = new QueuedExport(); + queuedExport.isReExport = false; + queuedExport.referencedName = referencedName; // -> internal name + queuedExport.member = member; + queuedExports.set(externalName, queuedExport); + + // export external element } else { - queuedExport.isForeign = true; - queuedExport.referencedName = (internalPath) + PATH_DELIMITER + member.identifier.name; + referencedName = (internalPath) + PATH_DELIMITER + member.externalIdentifier.name; + + // resolve right away if the export exists + if (this.exports.has(referencedName)) { + this.exports.set(externalName, this.exports.get(referencedName)); + return; + } + + // walk already known queued exports + const seen: Set = new Set(); + while (queuedExports.has(referencedName)) { + const queuedExport: QueuedExport = queuedExports.get(referencedName); + if (queuedExport.isReExport) { + if (this.exports.has(queuedExport.referencedName)) { + this.exports.set(externalName, this.exports.get(referencedName)); + return; + } + referencedName = queuedExport.referencedName; + if (seen.has(queuedExport)) + break; + seen.add(queuedExport); + } else { + if (this.elements.has(queuedExport.referencedName)) { + this.exports.set(externalName, this.elements.get(referencedName)); + return; + } + break; + } + } + + // otherwise queue it + if (queuedExports.has(externalName)) { + this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName); + return; + } + const queuedReExport: QueuedExport = new QueuedExport(); + queuedReExport.isReExport = true; + queuedReExport.referencedName = referencedName; // -> export name + queuedReExport.member = member; + queuedExports.set(externalName, queuedReExport); } - queuedExport.member = member; - queuedExports.set(exportName, queuedExport); } private initializeFunction(declaration: FunctionDeclaration): void { @@ -330,29 +391,48 @@ export class Program extends DiagnosticEmitter { } private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map, queuedImports: QueuedImport[]): void { - const importName: string = internalPath + PATH_DELIMITER + declaration.externalIdentifier.name; - let resolvedImportName: string = importName; - const seen: Set = new Set(); - while (queuedExports.has(resolvedImportName)) { - const queuedExport: QueuedExport = queuedExports.get(resolvedImportName); - resolvedImportName = queuedExport.referencedName; - if (seen.has(queuedExport)) - break; - seen.add(queuedExport); - } const internalName: string = declaration.internalName; - if (this.exports.has(resolvedImportName)) { // resolvable right away - if (this.elements.has(internalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); - else - this.elements.set(internalName, this.exports.get(resolvedImportName)); - } else { // points to yet unresolved export - const queuedImport: QueuedImport = new QueuedImport(); - queuedImport.internalName = internalName; - queuedImport.importName = importName; - queuedImport.declaration = declaration; - queuedImports.push(queuedImport); + if (this.elements.has(internalName)) { + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + return; } + + let referencedName: string = internalPath + PATH_DELIMITER + declaration.externalIdentifier.name; + + // resolve right away if the export exists + if (this.exports.has(referencedName)) { + this.elements.set(internalName, this.exports.get(referencedName)); + return; + } + + // walk already known queued exports + const seen: Set = new Set(); + while (queuedExports.has(referencedName)) { + const queuedExport: QueuedExport = queuedExports.get(referencedName); + if (queuedExport.isReExport) { + if (this.exports.has(queuedExport.referencedName)) { + this.elements.set(internalName, this.exports.get(referencedName)); + return; + } + referencedName = queuedExport.referencedName; + if (seen.has(queuedExport)) + break; + seen.add(queuedExport); + } else { + if (this.elements.has(queuedExport.referencedName)) { + this.elements.set(internalName, this.elements.get(referencedName)); + return; + } + break; + } + } + + // otherwise queue it + const queuedImport: QueuedImport = new QueuedImport(); + queuedImport.internalName = internalName; + queuedImport.referencedName = referencedName; + queuedImport.declaration = declaration; + queuedImports.push(queuedImport); } private initializeInterface(declaration: InterfaceDeclaration): void { diff --git a/src/tokenizer.ts b/src/tokenizer.ts index cd2908ae..9d8ac10e 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -724,10 +724,9 @@ export class Tokenizer extends DiagnosticEmitter { 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; + } else if (end < 0) + end = start; return new Range(this.source, start, end); } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..c766f909 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,29 @@ +Parser +------ + +Tests consist of a test case that is first parsed and then serialized again. The output is then compared to its respective fixture. + +``` +$> npm run test:parser [case name] +``` + +To recreate the fixtures: + +``` +$>npm run test:parser -- --create +``` + +Compiler +-------- + +Tests consist of a test case that is compiled to a module, converted to text format and then compared to its respective fixture. + +``` +$> npm run test:compiler [case name] +``` + +To recreate the fixtures: + +``` +$>npm run test:compiler -- --create +``` diff --git a/tests/binaryen.ts b/tests/binaryen.ts index 9a502693..68e740f8 100644 --- a/tests/binaryen.ts +++ b/tests/binaryen.ts @@ -1,6 +1,7 @@ -/// +/// + import "../src/glue/js"; -import { Type, Module, MemorySegment, BinaryOp } from "../src/binaryen"; +import { NativeType, Module, MemorySegment, BinaryOp } from "../src/module"; import { Target } from "../src/compiler"; import { U64 } from "../src/util"; @@ -11,33 +12,33 @@ mod.setMemory(1, Module.MAX_MEMORY_WASM32, [ MemorySegment.create(new Uint8Array(4), U64.fromI32(12)) ], Target.WASM32, "memory"); -const add = mod.addFunctionType("iii", Type.I32, [ Type.I32, Type.I32 ]); +const add = mod.addFunctionType("iii", NativeType.I32, [ NativeType.I32, NativeType.I32 ]); mod.addFunction("add", add, [], mod.createReturn( mod.createBinary(BinaryOp.AddI32, - mod.createGetLocal(0, Type.I32), - mod.createGetLocal(1, Type.I32) + mod.createGetLocal(0, NativeType.I32), + mod.createGetLocal(1, NativeType.I32) ) )); mod.addExport("add", "add"); -const lit = mod.addFunctionType("I", Type.I64, []); +const lit = mod.addFunctionType("I", NativeType.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)); +mod.addGlobal("42", NativeType.I32, false, mod.createI32(42)); -const aSwitch = mod.addFunctionType("ii", Type.I32, [ Type.I32 ]); +const aSwitch = mod.addFunctionType("ii", NativeType.I32, [ NativeType.I32 ]); const rl = mod.createRelooper(); -const b0 = rl.addBlockWithSwitch(mod.createNop(), mod.createGetLocal(0, Type.I32)); +const b0 = rl.addBlockWithSwitch(mod.createNop(), mod.createGetLocal(0, NativeType.I32)); let b1, b2, b3; rl.addBranchForSwitch(b0, b2 = rl.addBlock(mod.createReturn(mod.createI32(1))), [1]); // indexed branch rl.addBranchForSwitch(b0, b3 = rl.addBlock(mod.createReturn(mod.createI32(2))), [2]); // indexed branch rl.addBranch(b0, b1 = rl.addBlock(mod.createDrop(mod.createI32(0)))); // default branch rl.addBranch(b1, b2); -mod.addFunction("aSwitch", aSwitch, [ Type.I32 ], mod.createBlock(null, [ +mod.addFunction("aSwitch", aSwitch, [ NativeType.I32 ], mod.createBlock(null, [ rl.renderAndDispose(b0, 1), mod.createUnreachable() ])); diff --git a/tests/compiler.ts b/tests/compiler.ts index f22bd0be..0a85f333 100644 --- a/tests/compiler.ts +++ b/tests/compiler.ts @@ -1,72 +1,74 @@ +/// + +import * as fs from "fs"; +import * as path from "path"; +import * as chalk from "chalk"; +import * as glob from "glob"; + import "../src/glue/js"; import { Compiler } from "../src/compiler"; +import { Module } from "../src/module"; import { Parser } from "../src/parser"; +import { diff } from "./util/diff"; -/* const files: Map = new Map([ - ["main", `import { Test as TestAlias } from "./a"; export { TestAlias } from "./d"; if (1) {} export const a: i32 = 123;`], - ["a", `export { Test } from "./b";`], - ["b", `export { Test } from "./c";`], - ["c", `export enum Test { ONE = 1, TWO = 1 + 1 }`], - ["d", `export { Test as TestAlias } from "./b";`] -]); */ +// TODO: implement properly in module.ts +import * as binaryen from "binaryen"; +Module.prototype.toText = function(): string { + let old: any = (binaryen)["print"]; + let ret: string = ""; + (binaryen)["print"] = function(x: string): void { ret += x + "\n" }; + _BinaryenModulePrint(this.ref); + (binaryen)["print"] = old; + return ret; +} -const files: Map = new Map([ - ["main", -` - function add(a: i32, b: i32): i32 { return a + b; }; - export { add }; - export { sub as notadd } from "../other"; - 2+3; - export function switchMe(n: i32): i32 { - switch (n) { - case 0: - return 0; - default: - return 2; - case 1: - return 1; - case -1: - break; +const isCreate = process.argv[2] === "--create"; +const filter = process.argv.length > 2 && !isCreate ? "*" + process.argv[2] + "*.ts" : "*.ts"; + +glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { + if (filename.charAt(0) == "_" || filename.endsWith(".fixture.ts")) + return; + + console.log("Testing compiler/" + filename); + + const parser = new Parser(); + const sourceText = fs.readFileSync(__dirname + "/compiler/" + filename, { encoding: "utf8" }); + parser.parseFile(sourceText, filename, true); + let nextFile; + while ((nextFile = parser.nextFile()) !== null) { + let nextSourceText: string; + try { + nextSourceText = fs.readFileSync(path.join(__dirname, "compiler", nextFile + ".ts"), { encoding: "utf8" }); + } catch (e) { + nextSourceText = fs.readFileSync(path.join(__dirname, "compiler", nextFile, "index.ts"), { encoding: "utf8" }); } - return -1; + parser.parseFile(nextSourceText, nextFile, false); } - import { sub } from "../other"; - export function doCall(): void { - sub(1,2); + const program = parser.finish(); + const module = Compiler.compile(program); + const actual = module.toText() + "(;\n[program.elements]\n " + iterate(program.elements.keys()).join("\n ") + "\n[program.exports]\n " + iterate(program.exports.keys()).join("\n ") + "\n;)\n"; + const fixture = path.basename(filename, ".ts") + ".wast"; + + if (isCreate) { + fs.writeFileSync(__dirname + "/compiler/" + fixture, actual, { encoding: "utf8" }); + console.log("Created\n"); + } else { + const expected = fs.readFileSync(__dirname + "/compiler/" + fixture, { encoding: "utf8" }); + const diffs = diff("compiler/" + fixture, expected, actual); + if (diffs !== null) { + process.exitCode = 1; + console.log(diffs); + } else { + console.log("No changes\n"); + } } - export function doNaN(value: f32): bool { - return isNaN(0.3); +}); + +function iterate(it: IterableIterator): T[] { + let current: IteratorResult; + var arr: T[] = []; + while ((current = it.next()) && !current.done) { + arr.push(current.value); } - export function doRotl(value: u16): u8 { - return rotl(value, 2); - } -`], - - ["../other", -` - export function sub(a: i32, b: i32): i32 { return a - b + c; }; - let c: i32 = 42 >> 31; - 1+2; -`] -]); - -const parser = new Parser(); - -parser.parseFile(files.get("main"), "main", true); -do { - let nextFile = parser.nextFile(); - if (!nextFile) - break; - if (!files.has(nextFile)) - throw new Error("file not found: " + nextFile); - parser.parseFile(files.get(nextFile), nextFile, false); -} while(true); -const program = parser.finish(); -const compiler = new Compiler(program); -const module = compiler.compile(); -// console.log(program.elements.keys()); - -// module.optimize(); -module.validate(); -if (!module.noEmit) - _BinaryenModulePrint(module.ref); + return arr; +} \ No newline at end of file diff --git a/tests/compiler/export.ts b/tests/compiler/export.ts new file mode 100644 index 00000000..141c4558 --- /dev/null +++ b/tests/compiler/export.ts @@ -0,0 +1,15 @@ +export function add(a: i32, b: i32): i32 { + return a + b; +} + +function sub(a: i32, b: i32): i32 { + return a - b; +} + +export { sub as renamed_sub }; + +export let a: i32 = 1; + +let b: i32 = 2; + +export { b as renamed_b }; diff --git a/tests/compiler/export.wast b/tests/compiler/export.wast new file mode 100644 index 00000000..6ccef16d --- /dev/null +++ b/tests/compiler/export.wast @@ -0,0 +1,52 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (global $export/a i32 (i32.const 1)) + (global $export/b i32 (i32.const 2)) + (memory $0 1) + (data (i32.const 4) "\08\00\00\00") + (export "memory" (memory $0)) + (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.add + (get_local $0) + (get_local $1) + ) + ) + ) + (func $export/sub (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.sub + (get_local $0) + (get_local $1) + ) + ) + ) +) +(; +[program.elements] + clz + ctz + popcnt + rotl + rotr + abs + ceil + copysign + floor + max + min + nearest + sqrt + trunc + isNaN + isFinite + export/add + export/sub + export/a + export/b +[program.exports] + export/add + export/renamed_sub + export/a + export/renamed_b +;) diff --git a/tests/compiler/import.ts b/tests/compiler/import.ts new file mode 100644 index 00000000..04c5f08d --- /dev/null +++ b/tests/compiler/import.ts @@ -0,0 +1,3 @@ +import { add, renamed_sub as sub, a, renamed_b as b } from "./export"; + +add(a, b) + sub(b, a); diff --git a/tests/compiler/import.wast b/tests/compiler/import.wast new file mode 100644 index 00000000..c7e9693f --- /dev/null +++ b/tests/compiler/import.wast @@ -0,0 +1,72 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (type $v (func)) + (global $export/a i32 (i32.const 1)) + (global $export/b i32 (i32.const 2)) + (memory $0 1) + (data (i32.const 4) "\08\00\00\00") + (export "memory" (memory $0)) + (start $start) + (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.add + (get_local $0) + (get_local $1) + ) + ) + ) + (func $export/sub (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.sub + (get_local $0) + (get_local $1) + ) + ) + ) + (func $start (; 2 ;) (type $v) + (drop + (i32.add + (call $export/add + (get_global $export/a) + (get_global $export/b) + ) + (call $export/sub + (get_global $export/b) + (get_global $export/a) + ) + ) + ) + ) +) +(; +[program.elements] + clz + ctz + popcnt + rotl + rotr + abs + ceil + copysign + floor + max + min + nearest + sqrt + trunc + isNaN + isFinite + export/add + export/sub + export/a + export/b + import/add + import/sub + import/a + import/b +[program.exports] + export/add + export/renamed_sub + export/a + export/renamed_b +;) diff --git a/tests/compiler/reexport.ts b/tests/compiler/reexport.ts new file mode 100644 index 00000000..82e5ed4d --- /dev/null +++ b/tests/compiler/reexport.ts @@ -0,0 +1,7 @@ +export { add, renamed_sub } from "./export"; + +import { add as imported_add, renamed_sub as imported_sub } from "./export"; + +export { imported_add as renamed_add, imported_sub as rerenamed_sub }; + +imported_add(1, 2) + imported_sub(3, 4); diff --git a/tests/compiler/reexport.wast b/tests/compiler/reexport.wast new file mode 100644 index 00000000..5e51e50a --- /dev/null +++ b/tests/compiler/reexport.wast @@ -0,0 +1,72 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (type $v (func)) + (memory $0 1) + (data (i32.const 4) "\08\00\00\00") + (export "memory" (memory $0)) + (start $start) + (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.add + (get_local $0) + (get_local $1) + ) + ) + ) + (func $export/sub (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (return + (i32.sub + (get_local $0) + (get_local $1) + ) + ) + ) + (func $start (; 2 ;) (type $v) + (drop + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/sub + (i32.const 3) + (i32.const 4) + ) + ) + ) + ) +) +(; +[program.elements] + clz + ctz + popcnt + rotl + rotr + abs + ceil + copysign + floor + max + min + nearest + sqrt + trunc + isNaN + isFinite + export/add + export/sub + export/a + export/b + reexport/imported_add + reexport/imported_sub +[program.exports] + export/add + export/renamed_sub + export/a + export/renamed_b + reexport/add + reexport/renamed_sub + reexport/renamed_add + reexport/rerenamed_sub +;) diff --git a/tests/parser.ts b/tests/parser.ts new file mode 100644 index 00000000..f9384a2d --- /dev/null +++ b/tests/parser.ts @@ -0,0 +1,40 @@ +import * as fs from "fs"; +import * as chalk from "chalk"; +import * as glob from "glob"; + +import "../src/glue/js"; +import { Parser } from "../src/parser"; +import { diff } from "./util/diff"; + +const isCreate = process.argv[2] === "--create"; +const filter = process.argv.length > 2 && !isCreate ? "*" + process.argv[2] + "*.ts" : "**.ts"; + +glob.sync(filter, { cwd: __dirname + "/parser" }).forEach(filename => { + if (filename.charAt(0) == "_" || filename.endsWith(".fixture.ts")) + return; + + console.log("Testing parser/" + filename); + + const parser = new Parser(); + const sourceText = fs.readFileSync(__dirname + "/parser/" + filename, { encoding: "utf8" }).replace(/\r?\n/g, "\n").replace(/^\/\/.*\r?\n/mg, ""); + parser.parseFile(sourceText, filename, true); + + var sb: string[] = []; + parser.program.sources[0].serialize(sb); + const actual = sb.join(""); + const fixture = filename + ".fixture.ts"; + + if (isCreate) { + fs.writeFileSync(__dirname + "/parser/" + fixture, actual, { encoding: "utf8" }); + console.log("Created\n"); + } else { + const expected = fs.readFileSync(__dirname + "/parser/" + fixture, { encoding: "utf8" }); + const diffs = diff("parser/" + fixture, expected, actual); + if (diffs !== null) { + process.exitCode = 1; + console.log(diffs); + } else { + console.log("No changes\n"); + } + } +}); diff --git a/tests/parser/fixtures/class.ts b/tests/parser/class.ts similarity index 100% rename from tests/parser/fixtures/class.ts rename to tests/parser/class.ts diff --git a/tests/parser/class.ts.fixture.ts b/tests/parser/class.ts.fixture.ts new file mode 100644 index 00000000..53053378 --- /dev/null +++ b/tests/parser/class.ts.fixture.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/do.ts b/tests/parser/do.ts similarity index 100% rename from tests/parser/fixtures/do.ts rename to tests/parser/do.ts diff --git a/tests/parser/do.ts.fixture.ts b/tests/parser/do.ts.fixture.ts new file mode 100644 index 00000000..900eeaad --- /dev/null +++ b/tests/parser/do.ts.fixture.ts @@ -0,0 +1,3 @@ +do { +; +} while (a != b); diff --git a/tests/parser/fixtures/enum.ts b/tests/parser/enum.ts similarity index 100% rename from tests/parser/fixtures/enum.ts rename to tests/parser/enum.ts diff --git a/tests/parser/enum.ts.fixture.ts b/tests/parser/enum.ts.fixture.ts new file mode 100644 index 00000000..2a5021ae --- /dev/null +++ b/tests/parser/enum.ts.fixture.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/literals.ts b/tests/parser/fixtures/literals.ts deleted file mode 100644 index ef9f9232..00000000 --- a/tests/parser/fixtures/literals.ts +++ /dev/null @@ -1,42 +0,0 @@ -0; -1; -2; -3; -4; -5; -6; -7; -8; -9; -// 0x0; -// 0x1; -// 0x2; -// 0x3; -// 0x4; -// 0x5; -// 0x6; -// 0x7; -// 0x8; -// 0x9; -// 0xA; -// 0xB; -// 0xC; -// 0xD; -// 0xE; -// 0xF; -// 0xa; -// 0xb; -// 0xc; -// 0xd; -// 0xe; -// 0xf; -// 0o0; -// 0o1; -// 0o2; -// 0o3; -// 0o4; -// 0o5; -// 0o6; -// 0o7; -// 0b0; -// 0b1; diff --git a/tests/parser/fixtures/for.ts b/tests/parser/for.ts similarity index 100% rename from tests/parser/fixtures/for.ts rename to tests/parser/for.ts diff --git a/tests/parser/for.ts.fixture.ts b/tests/parser/for.ts.fixture.ts new file mode 100644 index 00000000..5f436e9a --- /dev/null +++ b/tests/parser/for.ts.fixture.ts @@ -0,0 +1,9 @@ +for (let i: i32 = 0; i < 10; ++i) { +; +} +for (i = 0; i < 10; ++i) { +; +} +for (;;) { +; +} diff --git a/tests/parser/fixtures/function.ts b/tests/parser/function.ts similarity index 100% rename from tests/parser/fixtures/function.ts rename to tests/parser/function.ts diff --git a/tests/parser/function.ts.fixture.ts b/tests/parser/function.ts.fixture.ts new file mode 100644 index 00000000..64221f7e --- /dev/null +++ b/tests/parser/function.ts.fixture.ts @@ -0,0 +1,7 @@ +function simple(): void { +} +function typeparams(a: V | null = null): void { +} +@decorator() +function withdecorator(): void { +} diff --git a/tests/parser/index.ts b/tests/parser/index.ts deleted file mode 100644 index 29eea633..00000000 --- a/tests/parser/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as fs from "fs"; -import * as diff from "diff"; -import * as chalk from "chalk"; -import * as glob from "glob"; - -import "../../src/glue/js"; -import { NodeKind, ExpressionStatement } from "../../src/ast"; -import { Parser } from "../../src/parser"; - -const filter = process.argv.length > 2 ? "*" + process.argv[2] + "*.ts" : "**.ts"; - -const files = glob.sync(filter, { cwd: __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[] = []; - 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.default.green : part.removed ? chalk.default.red : chalk.default.grey)(part.value)); - }); - } else { - console.log("No differences in " + filename + "."); - } - // parser.program.initialize(); - // console.log(parser.program.names); -}); diff --git a/tests/parser/literals.ts b/tests/parser/literals.ts new file mode 100644 index 00000000..4795d626 --- /dev/null +++ b/tests/parser/literals.ts @@ -0,0 +1,42 @@ +0; +1; +2; +3; +4; +5; +6; +7; +8; +9; +0x0; +0x1; +0x2; +0x3; +0x4; +0x5; +0x6; +0x7; +0x8; +0x9; +0xA; +0xB; +0xC; +0xD; +0xE; +0xF; +0xa; +0xb; +0xc; +0xd; +0xe; +0xf; +0o0; +0o1; +0o2; +0o3; +0o4; +0o5; +0o6; +0o7; +0b0; +0b1; diff --git a/tests/parser/literals.ts.fixture.ts b/tests/parser/literals.ts.fixture.ts new file mode 100644 index 00000000..80d52713 --- /dev/null +++ b/tests/parser/literals.ts.fixture.ts @@ -0,0 +1,42 @@ +0; +1; +2; +3; +4; +5; +6; +7; +8; +9; +0; +1; +2; +3; +4; +5; +6; +7; +8; +9; +10; +11; +12; +13; +14; +15; +10; +11; +12; +13; +14; +15; +0; +1; +2; +3; +4; +5; +6; +7; +0; +1; diff --git a/tests/parser/fixtures/while.ts b/tests/parser/while.ts similarity index 100% rename from tests/parser/fixtures/while.ts rename to tests/parser/while.ts diff --git a/tests/parser/while.ts.fixture.ts b/tests/parser/while.ts.fixture.ts new file mode 100644 index 00000000..d3d34101 --- /dev/null +++ b/tests/parser/while.ts.fixture.ts @@ -0,0 +1,9 @@ +while (1) { +; +} +while (true) { +; +} +while ("str") { +; +} diff --git a/tests/util/diff.ts b/tests/util/diff.ts new file mode 100644 index 00000000..e027647e --- /dev/null +++ b/tests/util/diff.ts @@ -0,0 +1,30 @@ +import * as JsDiff from "diff"; +import * as chalk from "chalk"; + +export function diff(filename: string, expected: string, actual: string): string | null { + const diff = JsDiff.structuredPatch(filename, filename, expected, actual, "expected", "actual", { context: 2 }); + if (!diff.hunks.length) + return null; + + const ret = []; + ret.push('--- ' + diff.oldHeader); + ret.push('+++ ' + diff.newHeader); + + for (let i = 0; i < diff.hunks.length; i++) { + const hunk = diff.hunks[i]; + ret.push( + '@@ -' + hunk.oldStart + ',' + hunk.oldLines + + ' +' + hunk.newStart + ',' + hunk.newLines + + ' @@' + ); + ret.push.apply(ret, hunk.lines.map(line => + line.charAt(0) === "+" + ? chalk.default.green(line) + : line.charAt(0) === "-" + ? line = chalk.default.red(line) + : line + )); + } + + return ret.join('\n') + '\n'; +}