diff --git a/package.json b/package.json index 693ecc51..2017873e 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "license": "Apache-2.0", "devDependencies": { "@types/chalk": "^0.4.31", "@types/diff": "^3.2.2", diff --git a/src/ast.ts b/src/ast.ts index 82aad4ba..13f668ef 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -112,6 +112,7 @@ export enum NodeKind { ENUMVALUE, // is also declaration EXPORT, EXPORTIMPORT, + EXPORTMEMBER, EXPRESSION, INTERFACE, FOR, @@ -875,11 +876,11 @@ export abstract class Statement extends Node { return stmt; } - static createExportMember(identifier: IdentifierExpression, asIdentifier: IdentifierExpression | null, range: Range): ExportMember { + static createExportMember(identifier: IdentifierExpression, externalIdentifier: 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; + (elem.externalIdentifier = externalIdentifier ? externalIdentifier : identifier).parent = elem; return elem; } @@ -1272,14 +1273,15 @@ export class ExportImportStatement extends Node { export class ExportMember extends Node { + kind = NodeKind.EXPORTMEMBER; identifier: IdentifierExpression; - asIdentifier: IdentifierExpression | null; + externalIdentifier: IdentifierExpression; serialize(sb: string[]): void { this.identifier.serialize(sb); - if (this.asIdentifier) { + if (this.externalIdentifier.name != this.identifier.name) { sb.push(" as "); - (this.asIdentifier).serialize(sb); + (this.externalIdentifier).serialize(sb); } } } diff --git a/src/compiler.ts b/src/compiler.ts index b1c2446b..48a55981 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,8 +1,9 @@ -import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp } from "./binaryen"; +import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp, Type as BinaryenType } from "./binaryen"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { hasModifier } from "./parser"; import { Program } from "./program"; -import { CharCode, U64 } from "./util"; +import { CharCode, I64, U64 } from "./util"; +import { Token } from "./tokenizer"; import { NodeKind, @@ -16,10 +17,12 @@ import { DoStatement, EmptyStatement, EnumDeclaration, + EnumValueDeclaration, ExpressionStatement, FunctionDeclaration, ForStatement, IfStatement, + ImportStatement, MethodDeclaration, ModifierKind, NamespaceDeclaration, @@ -93,6 +96,8 @@ export class Compiler extends DiagnosticEmitter { memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL memorySegments: MemorySegment[] = new Array(); + statements: BinaryenExpressionRef[] = new Array(); // TODO: make start function + static compile(program: Program, options: Options | null = null): Module { const compiler: Compiler = new Compiler(program, options); return compiler.compile(); @@ -141,6 +146,9 @@ export class Compiler extends DiagnosticEmitter { } break; + case NodeKind.IMPORT: + break; + case NodeKind.NAMESPACE: if (hasModifier(ModifierKind.EXPORT, (statement).modifiers)) { const ns: Namespace = Namespace.create(statement).exportAs((statement).identifier.name); @@ -162,7 +170,11 @@ export class Compiler extends DiagnosticEmitter { break; case NodeKind.EXPORT: - // obtain referenced declaration and export that + // TODO: obtain referenced declaration and export that + break; + + default: + this.statements.push(this.compileStatement(statement)); break; } } @@ -186,7 +198,17 @@ export class Compiler extends DiagnosticEmitter { } compileEnum(en: Enum): void { - throw new Error("not implemented"); + for (let i: i32 = 0, k = en.declaration.members.length; i < k; ++i) { + const declaration: EnumValueDeclaration = en.declaration.members[i]; + const name: string = this.program.mangleName(declaration); + const value: BinaryenExpressionRef = declaration.value + ? this.compileExpression(declaration.value, Type.i32) + : this.module.createI32(i); // TODO + this.module.addGlobal(name, BinaryenType.I32, false, value); + if (en.exportName != null) { + // TODO: WASM does not yet support non-function exports + } + } } compileFunction(fn: Function): void { @@ -230,6 +252,17 @@ export class Compiler extends DiagnosticEmitter { // statements + enterBreakContext(): string { + if (this.breakMinor == 0) + ++this.breakMajor; + ++this.breakMinor; + return this.breakMajor.toString() + "." + this.breakMinor.toString(); + } + + leaveBreakContext(): void { + --this.breakMinor; + } + compileStatement(statement: Statement): BinaryenExpressionRef { switch (statement.kind) { @@ -283,7 +316,7 @@ export class Compiler extends DiagnosticEmitter { 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); + return this.module.createBlock(null, children, BinaryenType.None); } compileBreakStatement(statement: BreakStatement): BinaryenExpressionRef { @@ -303,21 +336,20 @@ export class Compiler extends DiagnosticEmitter { } compileExpressionStatement(statement: ExpressionStatement): BinaryenExpressionRef { - const expression: BinaryenExpressionRef = this.compileExpression(statement.expression, Type.void); - return this.currentType == Type.void ? expression : this.module.createDrop(expression); + return this.compileExpression(statement.expression, Type.void); } 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; + const condition: BinaryenExpressionRef = statement.condition ? this.compileExpression(statement.condition, Type.i32) : 0; + const incrementor: BinaryenExpressionRef = statement.incrementor ? this.compileExpression(statement.incrementor, Type.void) : 0; throw new Error("not implemented"); } 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; + const ifFalse: BinaryenExpressionRef = statement.elseStatement ? this.compileStatement(statement.elseStatement) : 0; return this.module.createIf(condition, ifTrue, ifFalse); } @@ -346,72 +378,85 @@ export class Compiler extends DiagnosticEmitter { } compileWhileStatement(statement: WhileStatement): BinaryenExpressionRef { - throw new Error("not implemented"); + const condition: BinaryenExpressionRef = this.compileExpression(statement.condition, Type.i32); + const label: string = this.enterBreakContext(); + const breakLabel: string = "break$" + label; + const continueLabel: string = "continue$" + label; + const body: BinaryenExpressionRef = this.compileStatement(statement.statement); + this.leaveBreakContext(); + return this.module.createBlock(breakLabel, [ + this.module.createLoop(continueLabel, + this.module.createIf(condition, this.module.createBlock("", [ + body, + this.module.createBreak(continueLabel) + ])) + ) + ]); } // expressions - compileExpression(expression: Expression, resultType: Type): BinaryenExpressionRef { - this.currentType = resultType; + compileExpression(expression: Expression, contextualType: Type, convert: bool = true): BinaryenExpressionRef { + this.currentType = contextualType; let expr: BinaryenExpressionRef; switch (expression.kind) { case NodeKind.ASSERTION: - expr = this.compileAssertionExpression(expression, resultType); + expr = this.compileAssertionExpression(expression, contextualType); break; case NodeKind.BINARY: - expr = this.compileBinaryExpression(expression, resultType); + expr = this.compileBinaryExpression(expression, contextualType); break; case NodeKind.CALL: - expr = this.compileCallExpression(expression, resultType); + expr = this.compileCallExpression(expression, contextualType); break; case NodeKind.ELEMENTACCESS: - expr = this.compileElementAccessExpression(expression, resultType); + expr = this.compileElementAccessExpression(expression, contextualType); break; case NodeKind.IDENTIFIER: - expr = this.compileIdentifierExpression(expression, resultType); + expr = this.compileIdentifierExpression(expression, contextualType); break; case NodeKind.LITERAL: - expr = this.compileLiteralExpression(expression, resultType); + expr = this.compileLiteralExpression(expression, contextualType); break; case NodeKind.NEW: - expr = this.compileNewExpression(expression, resultType); + expr = this.compileNewExpression(expression, contextualType); break; case NodeKind.PARENTHESIZED: - expr = this.compileParenthesizedExpression(expression, resultType); + expr = this.compileParenthesizedExpression(expression, contextualType); break; case NodeKind.PROPERTYACCESS: - expr = this.compilePropertyAccessExpression(expression, resultType); + expr = this.compilePropertyAccessExpression(expression, contextualType); break; case NodeKind.SELECT: - expr = this.compileSelectExpression(expression, resultType); + expr = this.compileSelectExpression(expression, contextualType); break; case NodeKind.UNARYPOSTFIX: - expr = this.compileUnaryPostfixExpression(expression, resultType); + expr = this.compileUnaryPostfixExpression(expression, contextualType); break; case NodeKind.UNARYPREFIX: - expr = this.compileUnaryPrefixExpression(expression, resultType); + expr = this.compileUnaryPrefixExpression(expression, contextualType); break; default: throw new Error("unexpected expression kind"); } - if (this.currentType != resultType) { - expr = this.convertExpression(expr, this.currentType, resultType); - this.currentType = resultType; + if (convert && this.currentType != contextualType) { + expr = this.convertExpression(expr, this.currentType, contextualType); + this.currentType = contextualType; } return expr; @@ -580,6 +625,221 @@ export class Compiler extends DiagnosticEmitter { } compileBinaryExpression(expression: BinaryExpression, contextualType: Type): BinaryenExpressionRef { + let op: BinaryOp; + let left: BinaryenExpressionRef; + let right: BinaryenExpressionRef; + let compound: Token = 0; + + switch (expression.operator) { + + case Token.LESSTHAN: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.LtF32 + : this.currentType == Type.f64 + ? BinaryOp.LtF64 + : this.currentType.isLongInteger + ? BinaryOp.LtI64 + : BinaryOp.LtI32; + this.currentType = Type.bool; + break; + + case Token.GREATERTHAN: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.GtF32 + : this.currentType == Type.f64 + ? BinaryOp.GtF64 + : this.currentType.isLongInteger + ? BinaryOp.GtI64 + : BinaryOp.GtI32; + this.currentType = Type.bool; + break; + + case Token.LESSTHAN_EQUALS: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.LeF32 + : this.currentType == Type.f64 + ? BinaryOp.LeF64 + : this.currentType.isLongInteger + ? BinaryOp.LeI64 + : BinaryOp.LeI32; + this.currentType = Type.bool; + break; + + case Token.GREATERTHAN_EQUALS: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.GeF32 + : this.currentType == Type.f64 + ? BinaryOp.GeF64 + : this.currentType.isLongInteger + ? BinaryOp.GeI64 + : BinaryOp.GeI32; + this.currentType = Type.bool; + break; + + case Token.EQUALS_EQUALS: + case Token.EQUALS_EQUALS_EQUALS: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.EqF32 + : this.currentType == Type.f64 + ? BinaryOp.EqF64 + : this.currentType.isLongInteger + ? BinaryOp.EqI64 + : BinaryOp.EqI32; + this.currentType = Type.bool; + break; + + case Token.PLUS_EQUALS: + compound = Token.EQUALS; + case Token.PLUS: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.AddF32 + : this.currentType == Type.f64 + ? BinaryOp.AddF64 + : this.currentType.isLongInteger + ? BinaryOp.AddI64 + : BinaryOp.AddI32; + break; + + case Token.MINUS_EQUALS: + compound = Token.EQUALS; + case Token.MINUS: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.SubF32 + : this.currentType == Type.f64 + ? BinaryOp.SubF64 + : this.currentType.isLongInteger + ? BinaryOp.SubI64 + : BinaryOp.SubI32; + break; + + case Token.ASTERISK_EQUALS: + compound = Token.EQUALS; + case Token.ASTERISK: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.MulF32 + : this.currentType == Type.f64 + ? BinaryOp.MulF64 + : this.currentType.isLongInteger + ? BinaryOp.MulI64 + : BinaryOp.MulI32; + break; + + case Token.SLASH_EQUALS: + compound = Token.EQUALS; + case Token.SLASH: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType == Type.f32 + ? BinaryOp.DivF32 + : this.currentType == Type.f64 + ? BinaryOp.DivF64 + : this.currentType.isLongInteger + ? BinaryOp.DivI64 + : BinaryOp.DivI32; + break; + + case Token.PERCENT_EQUALS: + compound = Token.EQUALS; + case Token.PERCENT: + left = this.compileExpression(expression.left, contextualType, false); + right = this.compileExpression(expression.right, this.currentType); + if (this.currentType.isAnyFloat) + throw new Error("not implemented"); // TODO: fmod + op = this.currentType.isLongInteger + ? BinaryOp.RemI64 + : BinaryOp.RemI32; + break; + + case Token.LESSTHAN_LESSTHAN_EQUALS: + compound = Token.EQUALS; + case Token.LESSTHAN_LESSTHAN: + left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType.isLongInteger + ? BinaryOp.ShlI64 + : BinaryOp.ShlI32; + break; + + case Token.GREATERTHAN_GREATERTHAN_EQUALS: + compound = Token.EQUALS; + case Token.GREATERTHAN_GREATERTHAN: + left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType.isSignedInteger + ? this.currentType.isLongInteger + ? BinaryOp.ShrI64 + : BinaryOp.ShrI32 + : this.currentType.isLongInteger + ? BinaryOp.ShrU64 + : BinaryOp.ShrU32; + break; + + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: + compound = Token.EQUALS; + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: + left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.u64 : contextualType); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType.isLongInteger + ? BinaryOp.ShrU64 + : BinaryOp.ShrU32; + break; + + case Token.AMPERSAND_EQUALS: + compound = Token.EQUALS; + case Token.AMPERSAND: + left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType.isLongInteger + ? BinaryOp.AndI64 + : BinaryOp.AndI32; + break; + + case Token.BAR_EQUALS: + compound = Token.EQUALS; + case Token.BAR: + left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType.isLongInteger + ? BinaryOp.OrI64 + : BinaryOp.OrI32; + break; + + case Token.CARET_EQUALS: + compound = Token.EQUALS; + case Token.CARET: + left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : contextualType); + right = this.compileExpression(expression.right, this.currentType); + op = this.currentType.isLongInteger + ? BinaryOp.XorI64 + : BinaryOp.XorI32; + break; + + default: + throw new Error("not implemented"); + } + + return compound + ? this.compileAssignment(expression.left, this.module.createBinary(op, left, right)) + : this.module.createBinary(op, left, right); + } + + compileAssignment(expression: Expression, value: BinaryenExpressionRef): BinaryenExpressionRef { throw new Error("not implemented"); } @@ -599,23 +859,27 @@ export class Compiler extends DiagnosticEmitter { switch (expression.literalKind) { // case LiteralKind.ARRAY: - case LiteralKind.FLOAT: - if (contextualType == Type.f32) - return this.module.createF32((expression).value); + case LiteralKind.FLOAT: { + const floatValue: f64 = (expression).value; + if (contextualType == Type.f32 && (Math.fround(floatValue) as f64) == floatValue) + return this.module.createF32(floatValue); this.currentType = Type.f64; - return this.module.createF64((expression).value); + return this.module.createF64(floatValue); + } - case LiteralKind.INTEGER: - if (contextualType == Type.bool) - return this.module.createI32((expression).value.isOdd ? 1 : 0) + case LiteralKind.INTEGER: { + const intValue: I64 = (expression).value; + if (contextualType == Type.bool && (intValue.isZero || intValue.isOne)) + return this.module.createI32(intValue.isZero ? 0 : 1); 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); + return this.module.createI64(intValue.lo, intValue.hi); + if (!intValue.fitsInI32) { + this.currentType = Type.i64; + return this.module.createI64(intValue.lo, intValue.hi); + } + this.currentType = Type.i32; + return this.module.createI32(intValue.toI32()); + } // case LiteralKind.OBJECT: // case LiteralKind.REGEXP: @@ -644,10 +908,70 @@ export class Compiler extends DiagnosticEmitter { } compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): BinaryenExpressionRef { - throw new Error("not implemented"); + let operand: BinaryenExpressionRef; + let op: UnaryOp; + + switch (expression.operator) { + + case Token.PLUS_PLUS: + case Token.MINUS_MINUS: + + default: + throw new Error("not implemented"); + } + // return this.module.createBinary(op, operand); } compileUnaryPrefixExpression(expression: UnaryPrefixExpression, contextualType: Type): BinaryenExpressionRef { - throw new Error("not implemented"); + let operand: BinaryenExpressionRef; + let op: UnaryOp; + + switch (expression.operator) { + + case Token.PLUS: + return this.compileExpression(expression.expression, contextualType); + + case Token.MINUS: + operand = this.compileExpression(expression.expression, contextualType); + if (this.currentType == Type.f32) + op = UnaryOp.NegF32; + else if (this.currentType == Type.f64) + op = UnaryOp.NegF64; + else + return this.currentType.isLongInteger + ? this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0, 0), operand) + : this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), operand); + break; + + // case Token.PLUS_PLUS: + // case Token.MINUS_MINUS: + + case Token.EXCLAMATION: + operand = this.compileExpression(expression.expression, Type.bool, false); + if (this.currentType == Type.f32) { + this.currentType = Type.bool; + return this.module.createBinary(BinaryOp.EqF32, operand, this.module.createF32(0)); + } + if (this.currentType == Type.f64) { + this.currentType = Type.bool; + return this.module.createBinary(BinaryOp.EqF64, operand, this.module.createF64(0)); + } + op = this.currentType.isLongInteger + ? UnaryOp.EqzI64 // TODO: does this yield i64 0/1? + : UnaryOp.EqzI32; + this.currentType = Type.bool; + break; + + case Token.TILDE: + operand = this.compileExpression(expression.expression, contextualType.isAnyFloat ? Type.i64 : contextualType); + return this.currentType.isLongInteger + ? this.module.createBinary(BinaryOp.XorI64, operand, this.module.createI64(-1, -1)) + : this.module.createBinary(BinaryOp.XorI32, operand, this.module.createI32(-1)); + + default: + throw new Error("not implemented"); + } + + return this.module.createUnary(op, operand); } } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 5dfb9b14..b70b8891 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -151,6 +151,7 @@ export abstract class DiagnosticEmitter { 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); } error(code: DiagnosticCode, range: Range, arg0: string | null = null, arg1: string | null = null): void { diff --git a/src/parser.ts b/src/parser.ts index caf38bca..cb971a7b 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -11,7 +11,7 @@ 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 { I64, normalizePath, resolvePath } from "./util"; import { NodeKind, @@ -67,6 +67,7 @@ export class Parser extends DiagnosticEmitter { program: Program; backlog: string[] = new Array(); + seenlog: Set = new Set(); constructor() { super(); @@ -74,12 +75,14 @@ export class Parser extends DiagnosticEmitter { } parseFile(text: string, path: string, isEntry: bool): void { - if (path.startsWith("./")) - path = path.substring(2); + const normalizedPath: string = normalizePath(path); for (let i: i32 = 0, k: i32 = this.program.sources.length; i < k; ++i) - if (this.program.sources[i].path == path) + if (this.program.sources[i].normalizedPath == normalizedPath) throw Error("duplicate source"); + if (isEntry) + this.seenlog.add(normalizedPath); + const source: Source = new Source(path, text, isEntry); this.program.sources.push(source); @@ -189,6 +192,8 @@ export class Parser extends DiagnosticEmitter { finish(): Program { if (this.backlog.length) throw new Error("backlog is not empty"); + this.backlog = new Array(0); + this.seenlog.clear(); return this.program; } @@ -689,9 +694,14 @@ export class Parser extends DiagnosticEmitter { } let path: string | null = null; if (tn.skip(Token.FROM)) { - if (tn.skip(Token.STRINGLITERAL)) + if (tn.skip(Token.STRINGLITERAL)) { path = tn.readString(); - else { + const resolvedPath: string = resolvePath(normalizePath(path), tn.source.normalizedPath); + if (!this.seenlog.has(resolvedPath)) { + this.backlog.push(resolvedPath); + this.seenlog.add(resolvedPath); + } + } else { this.error(DiagnosticCode.String_literal_expected, tn.range()); return null; } @@ -743,6 +753,11 @@ export class Parser extends DiagnosticEmitter { if (tn.skip(Token.FROM)) { if (tn.skip(Token.STRINGLITERAL)) { const path: string = tn.readString(); + const resolvedPath: string = resolvePath(normalizePath(path), tn.source.normalizedPath); + if (!this.seenlog.has(resolvedPath)) { + this.backlog.push(resolvedPath); + this.seenlog.add(resolvedPath); + } const ret: ImportStatement = Statement.createImport(members, path, Range.join(startRange, tn.range())); tn.skip(Token.SEMICOLON); return ret; diff --git a/src/program.ts b/src/program.ts index d3f8fa4d..63550126 100644 --- a/src/program.ts +++ b/src/program.ts @@ -2,18 +2,21 @@ 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 { normalizePath, resolvePath, trimExtension } from "./util"; import { Node, NodeKind, SourceNode, ModifierKind, + IdentifierExpression, ClassDeclaration, DeclarationStatement, EnumDeclaration, EnumValueDeclaration, + ExportMember, + ExportStatement, FieldDeclaration, FunctionDeclaration, ImportDeclaration, @@ -27,15 +30,31 @@ import { } from "./ast"; +class QueuedExport { + importName: string; + member: ExportMember; +} + +class QueuedImport { + globalName: string; + importName: string; + declaration: ImportDeclaration; +} + export class Program extends DiagnosticEmitter { sources: Source[]; diagnosticsOffset: i32 = 0; target: Target = Target.WASM32; + // internal names to declarations names: Map = new Map(); + // type names to types types: Map = new Map(); + // export names to declarations (separate from internal names) + exports: Map = new Map(); + // reflection instances classes: Class[] = new Array(); enums: Enum[] = new Array(); functions: Function[] = new Array(); @@ -51,9 +70,10 @@ export class Program extends DiagnosticEmitter { this.target = target; initializeBasicTypes(this.types, target); - const importStatements: ImportStatement[] = new Array(); + const exportsMap: Map = new Map(); + const importsQueue: QueuedImport[] = new Array(); - // build a lookup map of global names to declarations + // build initial lookup maps of global and export names to declarations for (let i: i32 = 0, k: i32 = this.sources.length; i < k; ++i) { const source: Source = this.sources[i]; const statements: Statement[] = source.statements; @@ -69,13 +89,16 @@ export class Program extends DiagnosticEmitter { this.initializeEnum(statement); break; + case NodeKind.EXPORT: + this.initializeExports(statement, exportsMap); + break; + case NodeKind.FUNCTION: this.initializeFunction(statement); break; case NodeKind.IMPORT: - this.initializeImports(statement); - importStatements.push(statement); + this.initializeImports(statement, exportsMap, importsQueue); break; case NodeKind.INTERFACE: @@ -93,26 +116,52 @@ export class Program extends DiagnosticEmitter { } } - // 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 - } - } + // at this point exports map should be resolvable + for (let [key, val] of exportsMap.entries()) { + const seen: Set = new Set(); + while (exportsMap.has(val.importName)) { + val = exportsMap.get(val.importName); + if (seen.has(val)) + break; + seen.add(val); } + if (this.exports.has(val.importName)) + this.addExport(key, this.exports.get(val.importName)); + else + this.error(DiagnosticCode.Cannot_find_name_0, val.member.externalIdentifier.range, val.importName); + } + + // at this point imports queue should be resolvable + for (let i: i32 = 0, k: i32 = importsQueue.length; i < k; ++i) { + const queued: QueuedImport = importsQueue[i]; + const globalName: string = queued.globalName; + let importName: string = queued.importName; + const seen: Set = new Set(); + while (exportsMap.has(importName)) { + const val: QueuedExport = exportsMap.get(importName); + importName = val.importName; + if (seen.has(val)) + break; + seen.add(val); + } + if (this.exports.has(importName)) { + if (this.names.has(globalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, queued.declaration.identifier.range, globalName); + else { + const declaration: DeclarationStatement = this.exports.get(importName); + this.names.set(globalName, declaration); + // TODO: also mirror (non-private) member names? + } + } else + this.error(DiagnosticCode.Cannot_find_name_0, queued.declaration.externalIdentifier.range, importName); } } - initializeClass(declaration: ClassDeclaration): void { - this.addName(declaration); + private initializeClass(declaration: ClassDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) + this.addExport(globalName, declaration); const members: DeclarationStatement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -132,37 +181,88 @@ export class Program extends DiagnosticEmitter { } } - initializeField(declaration: FieldDeclaration): void { - this.addName(declaration); + private initializeField(declaration: FieldDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); } - initializeEnum(declaration: EnumDeclaration): void { - this.addName(declaration); + private initializeEnum(declaration: EnumDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) + this.addExport(globalName, 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); + private initializeEnumValue(declaration: EnumValueDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); } - initializeFunction(declaration: FunctionDeclaration): void { - this.addName(declaration); - } - - initializeImports(statement: ImportStatement): void { - const members: ImportDeclaration[] = statement.declarations; + private initializeExports(statement: ExportStatement, exportsMap: Map): void { + const members: ExportMember[] = statement.members; + const normalizedPath: string | null = statement.path == null ? null : normalizePath(statement.path); for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) - this.initializeImport(members[i]); + this.initializeExport(members[i], normalizedPath, exportsMap); } - initializeImport(declaration: ImportDeclaration): void { - this.addName(declaration); + private initializeExport(member: ExportMember, normalizedPath: string | null, exportsMap: Map): void { + const exportName: string = member.range.source.normalizedPath + "/" + member.externalIdentifier.name; + if (exportsMap.has(exportName)) + this.error(DiagnosticCode.Duplicate_identifier_0, member.externalIdentifier.range, exportName); + else { + const queued: QueuedExport = new QueuedExport(); + queued.importName = normalizedPath == null + ? member.range.source.normalizedPath + "/" + member.identifier.name // local + : (normalizedPath) + "/" + member.identifier.name; // re-export + queued.member = member; + exportsMap.set(exportName, queued); + } } - initializeInterface(declaration: InterfaceDeclaration): void { - this.addName(declaration); + private initializeFunction(declaration: FunctionDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) + this.addExport(globalName, declaration); + } + + private initializeImports(statement: ImportStatement, exportsMap: Map, importsQueue: QueuedImport[]): void { + const members: ImportDeclaration[] = statement.declarations; + const normalizedPath: string = normalizePath(statement.path); + for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { + const declaration: ImportDeclaration = members[i]; + this.initializeImport(declaration, normalizedPath, exportsMap, importsQueue); + } + } + + private initializeImport(declaration: ImportDeclaration, normalizedPath: string, exportsMap: Map, importsQueue: QueuedImport[]): void { + const importName: string = normalizedPath + "/" + declaration.externalIdentifier.name; + let resolvedImportName: string = importName; + while (exportsMap.has(resolvedImportName)) + resolvedImportName = (exportsMap.get(resolvedImportName)).importName; + const globalName: string = this.mangleName(declaration); + if (this.exports.has(resolvedImportName)) { // resolvable right away + if (this.names.has(globalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, globalName); + else + this.names.set(globalName, this.exports.get(resolvedImportName)); + } else { // points to yet unresolved export + const queued: QueuedImport = new QueuedImport(); + queued.globalName = globalName; + queued.importName = importName; + queued.declaration = declaration; + importsQueue.push(queued); + } + } + + private initializeInterface(declaration: InterfaceDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) + this.addExport(globalName, declaration); const members: Statement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -182,12 +282,16 @@ export class Program extends DiagnosticEmitter { } } - initializeMethod(declaration: MethodDeclaration): void { - this.addName(declaration); + private initializeMethod(declaration: MethodDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); } - initializeNamespace(declaration: NamespaceDeclaration): void { - this.addName(declaration); + private initializeNamespace(declaration: NamespaceDeclaration): void { + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) + this.addExport(globalName, declaration); const members: Statement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -214,7 +318,7 @@ export class Program extends DiagnosticEmitter { break; case NodeKind.VARIABLE: - this.initializeVariables(statement); + this.initializeVariables(statement, true); break; default: @@ -223,20 +327,30 @@ export class Program extends DiagnosticEmitter { } } - initializeVariables(statement: VariableStatement): void { + private initializeVariables(statement: VariableStatement, insideNamespace: bool = false): void { const declarations: VariableDeclaration[] = statement.members; + const isExport: bool = !insideNamespace && hasModifier(ModifierKind.EXPORT, statement.modifiers); for (let i: i32 = 0, k = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; - this.addName(declaration); + const globalName: string = this.mangleName(declaration); + this.addName(globalName, declaration); + if (isExport) + this.addExport(globalName, 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 + private addName(globalName: string, declaration: DeclarationStatement): void { + if (this.names.has(globalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, globalName); // recoverable else - this.names.set(name, declaration); + this.names.set(globalName, declaration); + } + + private addExport(globalName: string, declaration: DeclarationStatement): void { + if (this.exports.has(globalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, globalName); // recoverable + else + this.exports.set(globalName, declaration); } mangleName(declaration: DeclarationStatement): string { @@ -246,7 +360,7 @@ export class Program extends DiagnosticEmitter { switch (parent.kind) { case NodeKind.SOURCE: - return (parent).path + "/" + name; + return (parent).normalizedPath + "/" + name; case NodeKind.CLASS: { if ( @@ -314,12 +428,12 @@ function initializeBasicTypes(types: Map, target: Target) { 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("isize", target == Target.WASM64 ? Type.isize64 : Type.isize32); types.set("u8", Type.u8); types.set("u16", Type.u16); types.set("u32", Type.u32); types.set("u64", Type.u64); - types.set("usize", target == Target.WASM32 ? Type.usize32 : Type.usize64); + types.set("usize", target == Target.WASM64 ? Type.usize64 : Type.usize32); types.set("bool", Type.bool); types.set("void", Type.void); } diff --git a/src/reflection.ts b/src/reflection.ts index 6e355948..2bead97a 100644 --- a/src/reflection.ts +++ b/src/reflection.ts @@ -42,7 +42,7 @@ import { import { DiagnosticMessage } from "./diagnostics"; import { Token, Tokenizer, Range } from "./tokenizer"; import { hasModifier } from "./parser"; -import { normalizePath } from "./util"; +import { normalizePath, trimExtension } from "./util"; export abstract class Base { @@ -146,7 +146,7 @@ export class Type extends Base { export class Source extends SourceNode { text: string; - tokenizer: Tokenizer | null; + tokenizer: Tokenizer | null = null; statements: Statement[]; isEntry: bool; normalizedPath: string; @@ -158,7 +158,7 @@ export class Source extends SourceNode { this.text = text; this.statements = new Array(); this.isEntry = isEntry; - this.normalizedPath = normalizePath(path); + this.normalizedPath = normalizePath(trimExtension(path)); } get isDeclaration(): bool { return !this.isEntry && this.path.endsWith(".d.ts"); } diff --git a/src/tsconfig.json b/src/tsconfig.json index 33989936..841b22d2 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -9,6 +9,7 @@ "types": [ "node" ], + "downlevelIteration": true, "strictNullChecks": true, "alwaysStrict": true, "outDir": "../out" diff --git a/src/util.ts b/src/util.ts index ceb3efa9..042008ff 100644 --- a/src/util.ts +++ b/src/util.ts @@ -226,3 +226,10 @@ export function dirname(normalizedPath: string, separator: CharCode = CharCode.S export function resolvePath(normalizedPath: string, normalizedOrigin: string, separator: CharCode = CharCode.SLASH): string { return normalizePath(dirname(normalizedOrigin, separator) + String.fromCharCode(separator) + normalizedPath); } + +export function trimExtension(path: string): string { + const len: i32 = path.length; + if (len > 3 && path.charCodeAt(len - 3) == CharCode.DOT && (path.charCodeAt(len - 2) == CharCode.t || path.charCodeAt(len - 2) == CharCode.a) && path.charCodeAt(len - 1) == CharCode.s) + return path.substring(0, len - 3); + return path; +} diff --git a/src/util/i64.ts b/src/util/i64.ts index f8a31509..ab61b573 100644 --- a/src/util/i64.ts +++ b/src/util/i64.ts @@ -31,6 +31,10 @@ export class I64 { return this.lo == 0 && this.hi == 0; } + get isOne(): bool { + return this.lo == 1 && this.hi == 0; + } + get isPositive(): bool { return this.hi >= 0; } @@ -47,6 +51,10 @@ export class I64 { return (this.lo & 1) == 0; } + get fitsInI32(): bool { + return this.hi == 0 || (this.hi == -1 && this.lo < 0); + } + toI32(): i32 { return this.lo; } diff --git a/tests/compiler.ts b/tests/compiler.ts new file mode 100644 index 00000000..bb1ec3f3 --- /dev/null +++ b/tests/compiler.ts @@ -0,0 +1,37 @@ +import "../src/glue/js"; +import { Compiler } from "../src/compiler"; +import { Parser } from "../src/parser"; + +const files: Map = new Map([ + ["main", `import { Test } from "./a"; export { TestAlias } from "./d";`], + ["a", `export { Test } from "./b";`], + ["b", `export { Test } from "./c";`], + ["c", `export enum Test { ONE = 1 }`], + ["d", `export { Test as TestAlias } from "./b";`] +]); + +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("names", program.names); +console.log("exports", program.exports); + +// module.optimize(); +// module.validate(); // global initializers can't use i32.add etc. yet +/* _BinaryenModulePrint(module.ref); + +console.log("--- statements ---"); +compiler.statements.forEach(stmt => { + _BinaryenExpressionPrint(stmt); +}); */ \ No newline at end of file