diff --git a/assembly.d.ts b/assembly.d.ts new file mode 100644 index 00000000..1b85909e --- /dev/null +++ b/assembly.d.ts @@ -0,0 +1,107 @@ +// types + +/** An 8-bit signed integer. */ +declare type i8 = number; +/** A 16-bit signed integer. */ +declare type i16 = number; +/** A 32-bit signed integer. */ +declare type i32 = number; +/** A 64-bit signed integer. */ +declare type i64 = number; +/** A 32-bit signed integer when targeting 32-bit WebAssembly or a 64-bit signed integer when targeting 64-bit WebAssembly. */ +declare type isize = number; +/** An 8-bit unsigned integer. */ +declare type u8 = number; +/** A 16-bit unsigned integer. */ +declare type u16 = number; +/** A 32-bit unsigned integer. */ +declare type u32 = number; +/** A 64-bit unsigned integer. */ +declare type u64 = number; +/** A 32-bit unsigned integer when targeting 32-bit WebAssembly or a 64-bit unsigned integer when targeting 64-bit WebAssembly. */ +declare type usize = number; +/** A 1-bit unsigned integer. */ +declare type bool = any; // sic +/** A 32-bit float. */ +declare type f32 = number; +/** A 64-bit float. */ +declare type f64 = number; + +// builtins + +/** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */ +declare function rotl(value: T, shift: T): T; +/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */ +declare function rotr(value: T, shift: T): T; +/** Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero. */ +declare function clz(value: T): T; +/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero. */ +declare function ctz(value: T): T; +/** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */ +declare function popcnt(value: T): T; + +/** Computes the absolute value of a 32-bit or 64-bit float. */ +declare function abs(value: T): T; +/** Performs the ceiling operation on a 32-bit or 64-bit float. */ +declare function ceil(value: T): T; +/** Performs the floor operation on a 32-bit or 64-bit float. */ +declare function floor(value: T): T; +/** Calculates the square root of a 32-bit or 64-bit float. */ +declare function sqrt(value: T): T; +/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */ +declare function trunc(value: T): T; +/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */ +declare function nearest(value: T): T; +/** Determines the minimum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ +declare function min(left: T, right: T): T; +/** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ +declare function max(left: T, right: T): T; +/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */ +declare function copysign(x: T, y: T): T; + +/** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */ +declare function reinterpret(value: T1): T2; +/** Returns the current memory size in units of pages. One page is 64kb. */ +declare function current_memory(): i32; +/** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */ +declare function grow_memory(value: i32): i32; +/** Emits an unreachable operation that results in a runtime error when executed. */ +declare function unreachable(): void; + +/** Loads a value of the specified type from memory. */ +declare function load(offset: usize): T; +/** Stores a value of the specified type to memory. */ +declare function store(offset: usize, value: T): void; +/** Determines the byte size of the specified core or class type. Compiles to a constant. */ +declare function sizeof(): usize; +/** Gets the underlying pointer value of a class type. */ +declare function pointerof(cls: T): usize; +/** Creates a class type from its underlying pointer value. */ +declare function classof(ptr: usize): T; + +// standard library + +/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */ +declare const NaN: number; +/** Positive infinity as a 32-bit or 64-bit float depending on context. */ +declare const Infinity: number; + +/** Tests if a 32-bit or 64-bit float is NaN. */ +declare function isNaN(value: T): bool; +/** Tests if a 32-bit or 64-bit float is finite, that is not NaN or +/-Infinity. */ +declare function isFinite(value: T): bool; + +/** A decorator marking a function or class as global. */ +declare function global(name?: string): any; +/** A decorator marking a function as ideally being inlined. */ +declare function inline(): any; +/** A decorator marking a class that manually manages its memory. */ +declare function allocates(): any; +declare function operator(token: string, fn: any): any; + +/// +/// +/// +/// +/// +/// diff --git a/src/ast.ts b/src/ast.ts index 12ca05c9..5555cee9 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -66,6 +66,7 @@ */ +import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER, PARENT_SUBST, STATIC_DELIMITER, INSTANCE_DELIMITER } from "./constants"; import { Token, Tokenizer, operatorTokenToString, Range } from "./tokenizer"; import { CharCode, I64, normalizePath, resolvePath } from "./util"; @@ -139,59 +140,6 @@ export enum NodeKind { 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.FALSE: return "FALSE"; - 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.NULL: return "NULL"; - case NodeKind.FIELD: return "PROPERTY"; - case NodeKind.RETURN: return "RETURN"; - case NodeKind.SUPER: return "SUPER"; - case NodeKind.SWITCH: return "SWITCH"; - case NodeKind.THIS: return "THIS"; - case NodeKind.THROW: return "THROW"; - case NodeKind.TRUE: return "TRUE"; - case NodeKind.TRY: return "TRY"; - case NodeKind.VARIABLE: return "VARIABLE"; - case NodeKind.VARIABLEDECLARATION: return "VARIABLEDECLARATION"; - case NodeKind.WHILE: return "WHILE"; - default: return ""; - } -} - // types export class TypeNode extends Node { @@ -416,18 +364,6 @@ export const enum LiteralKind { 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; @@ -840,24 +776,6 @@ export enum ModifierKind { 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 { @@ -943,6 +861,7 @@ export abstract class Statement extends Node { for (i = 0, k = (stmt.members = members).length; i < k; ++i) members[i].parent = stmt; stmt.path = path; stmt.normalizedPath = path ? resolvePath(normalizePath(path.value), range.source.normalizedPath) : null; + stmt.internalPath = stmt.normalizedPath ? mangleInternalPath(stmt.normalizedPath) : null; return stmt; } @@ -984,6 +903,7 @@ export abstract class Statement extends Node { for (let i: i32 = 0, k: i32 = (stmt.declarations = declarations).length; i < k; ++i) declarations[i].parent = stmt; stmt.path = path; stmt.normalizedPath = resolvePath(normalizePath(path.value), range.source.normalizedPath); + stmt.internalPath = mangleInternalPath(stmt.normalizedPath); return stmt; } @@ -995,7 +915,7 @@ export abstract class Statement extends Node { return elem; } - static createInterface(modifiers: Modifier[], extendsType: TypeNode | null, members: Statement[], range: Range): InterfaceDeclaration { + static createInterface(modifiers: Modifier[], extendsType: TypeNode | null, members: DeclarationStatement[], range: Range): InterfaceDeclaration { const stmt: InterfaceDeclaration = new InterfaceDeclaration(); stmt.range = range; let i: i32, k: i32; @@ -1164,6 +1084,7 @@ export class Source extends Node { parent = null; path: string; normalizedPath: string; + internalPath: string; statements: Statement[]; text: string; @@ -1174,6 +1095,7 @@ export class Source extends Node { super(); this.path = path; this.normalizedPath = normalizePath(path, true); + this.internalPath = mangleInternalPath(this.normalizedPath); this.statements = new Array(); this.range = new Range(this, 0, text.length); this.text = text; @@ -1199,14 +1121,15 @@ export abstract class DeclarationStatement extends Statement { identifier: IdentifierExpression; modifiers: Modifier[] | null; - private _cachedInternalName: string | null = null; - globalExportName: string | null = null; - get internalName(): string { - if (this._cachedInternalName == null) - this._cachedInternalName = mangleInternalName(this); - return this._cachedInternalName; - } + protected _cachedInternalName: string | null = null; + + get internalName(): string { return this._cachedInternalName === null ? this._cachedInternalName = mangleInternalName(this) : this._cachedInternalName; } +} + +export abstract class VariableLikeDeclarationStatement extends DeclarationStatement { + type: TypeNode | null; + initializer: Expression | null; } export class BlockStatement extends Statement { @@ -1250,6 +1173,16 @@ export class ClassDeclaration extends DeclarationStatement { members: DeclarationStatement[]; decorators: DecoratorStatement[]; + get internalName(): string { + if (this._cachedInternalName !== null) + return this._cachedInternalName; + const globalDecorator: DecoratorStatement | null = getDecoratorByName("global", this.decorators); + if (globalDecorator && globalDecorator.expression.kind == NodeKind.IDENTIFIER && (globalDecorator.expression).name == "global") + return this._cachedInternalName = this.identifier.name; + else + return this._cachedInternalName = mangleInternalName(this); + } + serialize(sb: string[]): void { let i: i32, k: i32; for (i = 0, k = this.decorators.length; i < k; ++i) { @@ -1429,6 +1362,7 @@ export class ExportStatement extends Statement { members: ExportMember[]; path: StringLiteralExpression | null; normalizedPath: string | null; + internalPath: string | null; serialize(sb: string[]): void { let i: i32, k: i32; @@ -1461,11 +1395,9 @@ export class ExpressionStatement extends Statement { } } -export class FieldDeclaration extends DeclarationStatement { +export class FieldDeclaration extends VariableLikeDeclarationStatement { kind = NodeKind.FIELD; - type: TypeNode | null; - initializer: Expression | null; decorators: DecoratorStatement[]; serialize(sb: string[]): void { @@ -1527,6 +1459,16 @@ export class FunctionDeclaration extends DeclarationStatement { statements: Statement[] | null; decorators: DecoratorStatement[]; + get internalName(): string { + if (this._cachedInternalName !== null) + return this._cachedInternalName; + const globalDecorator: DecoratorStatement | null = getDecoratorByName("global", this.decorators); + if (globalDecorator && globalDecorator.expression.kind == NodeKind.IDENTIFIER && (globalDecorator.expression).name == "global") + return this._cachedInternalName = this.identifier.name; + else + return this._cachedInternalName = mangleInternalName(this); + } + serialize(sb: string[]): void { let i: i32, k: i32; for (i = 0, k = this.decorators.length; i < k; ++i) { @@ -1625,6 +1567,7 @@ export class ImportStatement extends Statement { declarations: ImportDeclaration[]; path: StringLiteralExpression; normalizedPath: string; + internalPath: string; serialize(sb: string[]): void { sb.push("import {\n"); @@ -1638,12 +1581,9 @@ export class ImportStatement extends Statement { } } -export class InterfaceDeclaration extends DeclarationStatement { +export class InterfaceDeclaration extends ClassDeclaration { kind = NodeKind.INTERFACE; - typeParameters: TypeParameter[]; - extendsType: TypeNode | null; - members: Statement[]; serialize(sb: string[]): void { let i: i32, k: i32; @@ -1872,12 +1812,10 @@ export class TryStatement extends Statement { } } -export class VariableDeclaration extends DeclarationStatement { +export class VariableDeclaration extends VariableLikeDeclarationStatement { kind = NodeKind.VARIABLEDECLARATION; modifiers = null; - type: TypeNode | null; - initializer: Expression | null; serialize(sb: string[]): void { this.identifier.serialize(sb); @@ -1940,21 +1878,53 @@ export function hasModifier(kind: ModifierKind, modifiers: Modifier[] | null): b return false; } +export function getDecoratorByName(name: string, decorators: DecoratorStatement[]): DecoratorStatement | null { + for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { + const decorator: DecoratorStatement = decorators[i]; + const expression: Expression = decorator.expression; + if (expression.kind == NodeKind.IDENTIFIER && (expression).name == name) + return decorator; + } + return null; +} + export function serialize(node: Node, indent: i32 = 0): string { const sb: string[] = new Array(); // shared builder could grow too much node.serialize(sb); return sb.join(""); } +export function mangleInternalPath(path: string): string { + if (PATH_DELIMITER.charCodeAt(0) != CharCode.SLASH) + path = path.replace("/", PATH_DELIMITER); + if (PARENT_SUBST != "..") + path = path.replace("..", PARENT_SUBST); + return path; +} + export function mangleInternalName(declaration: DeclarationStatement): string { let name: string = declaration.identifier.name; + let modifiers: Modifier[] | null; + if (declaration.kind == NodeKind.METHOD && (modifiers = declaration.modifiers)) { + for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i) { + const modifier: Modifier = modifiers[i]; + if (modifier.modifierKind == ModifierKind.GET) { + name = GETTER_PREFIX + name; + break; + } + else if (modifier.modifierKind == ModifierKind.SET) { + name = SETTER_PREFIX + name; + break; + } + } + } if (!declaration.parent) return name; if (declaration.parent.kind == NodeKind.CLASS) - return (declaration.parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? "." : "#") + name; + return (declaration.parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name; if (declaration.parent.kind == NodeKind.NAMESPACE || declaration.parent.kind == NodeKind.ENUM) - return (declaration.parent).internalName + "." + name; - return declaration.range.source.normalizedPath + "/" + name; + return (declaration.parent).internalName + STATIC_DELIMITER + name; + return declaration.range.source.internalPath + PATH_DELIMITER + name; } function builderEndsWith(sb: string[], code: CharCode): bool { diff --git a/src/compiler.ts b/src/compiler.ts index 8d5d96a8..fac2107f 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,6 +1,7 @@ import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp, Type as BinaryenType, Relooper } from "./binaryen"; +import { PATH_DELIMITER } from "./constants"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; -import { Program } from "./program"; +import { Program, ClassTemplate, Element, ElementKind, Enum, FunctionTemplate, FunctionInstance, Global, Local, Namespace, Parameter } from "./program"; import { CharCode, I64, U64, normalizePath, sb } from "./util"; import { Token } from "./tokenizer"; import { @@ -38,6 +39,7 @@ import { SwitchStatement, ThrowStatement, TryStatement, + VariableLikeDeclarationStatement, VariableDeclaration, VariableStatement, WhileStatement, @@ -67,12 +69,8 @@ import { } from "./ast"; import { - ClassType, - FunctionType, - LocalType, Type, TypeKind, - typeArgumentsToString } from "./types"; @@ -86,7 +84,7 @@ export enum Target { export class Options { /** WebAssembly target. Defaults to {@link Target.WASM32}. */ target: Target = Target.WASM32; - /** If true, performs compilation as usual but doesn't produce any output (all calls to Binaryen are nops). */ + /** If true, performs compilation as usual but doesn't produce any output (all calls to Binaryen become nops). */ noEmit: bool = false; /** If true, compiles everything instead of just reachable code. */ noTreeShaking: bool = false; @@ -98,22 +96,17 @@ export class Compiler extends DiagnosticEmitter { options: Options; module: Module; - startFunction: FunctionType = new FunctionType([], [], Type.void); + startFunction: FunctionInstance; startFunctionBody: BinaryenExpressionRef[] = new Array(); currentType: Type = Type.void; - currentClass: ClassType | null = null; - currentFunction: FunctionType = this.startFunction; + currentFunction: FunctionInstance; disallowContinue: bool = true; memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL memorySegments: MemorySegment[] = new Array(); files: Set = new Set(); - classes: Map = new Map(); - enums: Set = new Set(); - functions: Map = new Map(); - globals: Map = new Map(); static compile(program: Program, options: Options | null = null): Module { const compiler: Compiler = new Compiler(program, options); @@ -125,6 +118,10 @@ export class Compiler extends DiagnosticEmitter { this.program = program; this.options = options ? options : new Options(); this.module = this.options.noEmit ? Module.createStub() : Module.create(); + const startFunctionTemplate: FunctionTemplate = new FunctionTemplate(program, "start", null); + const startFunctionInstance: FunctionInstance = new FunctionInstance(startFunctionTemplate, startFunctionTemplate.internalName, [], [], Type.void, null); + this.currentFunction = this.startFunction = startFunctionInstance; + this.memoryOffset = new U64(2 * (this.options.target == Target.WASM64 ? 8 : 4), 0); // leave space for `null` and heapStart (both of usize type) } compile(): Module { @@ -134,10 +131,12 @@ export class Compiler extends DiagnosticEmitter { program.initialize(this.options.target); // compile entry file (exactly one, usually) - for (let i: i32 = 0, k = program.sources.length; i < k; ++i) { - const source: Source = program.sources[i]; + const sources: Source[] = program.sources; + let i: i32, k: i32 = sources.length; + for (i = 0; i < k; ++i) { + const source: Source = sources[i]; if (source.isEntry) - this.compileFile(source); + this.compileSource(source); } // make start function if not empty @@ -146,25 +145,64 @@ export class Compiler extends DiagnosticEmitter { if (!typeRef) typeRef = this.module.addFunctionType("v", BinaryenType.None, []); this.module.setStart( - this.module.addFunction("start", typeRef, typesToBinaryenTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) + this.module.addFunction(this.startFunction.template.internalName, typeRef, typesToBinaryenTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) ); } - // set up memory size and static segments + // set up memory + // store heapStart at `sizeof()` (that is right after `null`) as an usize const initial: U64 = this.memoryOffset.clone(); + let heapStartBuffer: Uint8Array; + let heapStartOffset: i32; + if (this.options.target == Target.WASM64) { + heapStartBuffer = new Uint8Array(8); + heapStartOffset = 8; + heapStartBuffer[0] = (initial.lo ) as u8; + heapStartBuffer[1] = (initial.lo >>> 8) as u8; + heapStartBuffer[2] = (initial.lo >>> 16) as u8; + heapStartBuffer[3] = (initial.lo >>> 24) as u8; + heapStartBuffer[4] = (initial.hi ) as u8; + heapStartBuffer[5] = (initial.hi >>> 8) as u8; + heapStartBuffer[6] = (initial.hi >>> 16) as u8; + heapStartBuffer[7] = (initial.hi >>> 24) as u8; + } else { + if (!initial.fitsInU32) + throw new Error("memory size overflow"); + heapStartBuffer = new Uint8Array(4); + heapStartOffset = 4; + heapStartBuffer[0] = (initial.lo ) as u8; + heapStartBuffer[1] = (initial.lo >>> 8) as u8; + heapStartBuffer[2] = (initial.lo >>> 16) as u8; + heapStartBuffer[3] = (initial.lo >>> 24) as u8; + } + this.memorySegments.push(MemorySegment.create(heapStartBuffer, new U64(heapStartOffset, 0))); + // determine initial page size const initialOverlaps: U64 = initial.clone(); initialOverlaps.and32(0xffff); if (!initialOverlaps.isZero) { initial.or32(0xffff); initial.add32(1); } - initial.shru32(16); + initial.shru32(16); // initial size in 64k pages this.module.setMemory(initial.toI32(), Module.MAX_MEMORY_WASM32 /* TODO: not WASM64 compatible yet */, this.memorySegments, this.options.target, "memory"); return this.module; } - compileFile(source: Source): void { + // sources + + compileSourceByPath(normalizedPath: string, reportNode: Node): void { + for (let i: i32 = 0, k: i32 = this.program.sources.length; i < k; ++i) { + const importedSource: Source = this.program.sources[i]; + if (importedSource.normalizedPath == normalizedPath) { + this.compileSource(importedSource); + return; + } + } + this.error(DiagnosticCode.File_0_not_found, reportNode.range, normalizedPath); + } + + compileSource(source: Source): void { if (this.files.has(source.normalizedPath)) return; this.files.add(source.normalizedPath); @@ -177,48 +215,45 @@ export class Compiler extends DiagnosticEmitter { case NodeKind.CLASS: if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) && !(statement).typeParameters.length) - this.compileClass(statement, []); + this.compileClassDeclaration(statement, []); break; case NodeKind.ENUM: if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) - this.compileEnum(statement); + this.compileEnumDeclaration(statement); break; case NodeKind.FUNCTION: if ((noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) && !(statement).typeParameters.length) - this.compileFunction(statement, []); + this.compileFunctionDeclaration(statement, []); break; case NodeKind.IMPORT: - this.compileFileByPath((statement).normalizedPath, (statement).path); + this.compileSourceByPath((statement).normalizedPath, (statement).path); break; case NodeKind.NAMESPACE: if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) - this.compileNamespace(statement); + this.compileNamespaceDeclaration(statement); break; case NodeKind.VARIABLE: if (noTreeShaking || isEntry && hasModifier(ModifierKind.EXPORT, (statement).modifiers)) - this.compileGlobals(statement); + this.compileVariableStatement(statement); break; case NodeKind.EXPORT: if ((statement).path) - this.compileFileByPath(((statement).path).value, (statement).path); + this.compileSourceByPath(((statement).path).value, (statement).path); if (noTreeShaking || isEntry) - this.compileExports(statement); + this.compileExportStatement(statement); break; // otherwise a top-level statement that is part of the start function's body default: { - const previousClass: ClassType | null = this.currentClass; - const previousFunction: FunctionType = this.currentFunction; - this.currentClass = null; + const previousFunction: FunctionInstance = this.currentFunction; this.currentFunction = this.startFunction; this.startFunctionBody.push(this.compileStatement(statement)); - this.currentClass = previousClass; this.currentFunction = previousFunction; break; } @@ -226,201 +261,170 @@ export class Compiler extends DiagnosticEmitter { } } - compileFileByPath(normalizedPath: string, reportNode: Node): void { - for (let j: i32 = 0, l: i32 = this.program.sources.length; j < l; ++j) { - const importedSource: Source = this.program.sources[j]; - if (importedSource.normalizedPath == normalizedPath) { - this.compileFile(importedSource); - return; - } + // globals + + compileGlobalDeclaration(declaration: VariableDeclaration, isConst: bool): bool { + const element: Element | null = this.program.elements.get(declaration.internalName); + if (!element || element.kind != ElementKind.GLOBAL) + throw new Error("unexpected missing global"); + return this.compileGlobal(element); + } + + compileGlobal(element: Global): bool { + if (element.compiled) + return true; + const declaration: VariableLikeDeclarationStatement | null = element.declaration; + let type: Type | null = element.type; + if (!type) { + if (!declaration) + throw new Error("unexpected missing declaration"); + if (!declaration.type) + return false; // TODO: infer type? currently reported by parser + type = this.program.resolveType(declaration.type); // reports + if (!type) + return false; + element.type = type; } - this.error(DiagnosticCode.File_0_not_found, reportNode.range, normalizedPath); - } - - compileClass(declaration: ClassDeclaration, typeArguments: Type[]): void { - throw new Error("not implemented"); - } - - compileEnum(declaration: EnumDeclaration): void { - const internalName: string = declaration.internalName; - if (this.enums.has(internalName)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); - return; - } - this.enums.add(internalName); - const valueDeclarations: EnumValueDeclaration[] = declaration.members; - let previousValueName: string | null = null; - for (let i: i32 = 0, k: i32 = valueDeclarations.length; i < k; ++i) - previousValueName = this.compileEnumValue(valueDeclarations[i], previousValueName); - } - - compileEnumValue(declaration: EnumValueDeclaration, previousName: string | null): string { - const internalName: string = declaration.internalName; - // TODO: WASM does not support complex initializers for globals yet, hence we make such globals mutable and initialize in start + const binaryenType: BinaryenType = typeToBinaryenType(type); let initializer: BinaryenExpressionRef; let initializeInStart: bool; - if (declaration.value) { - initializer = this.compileExpression(declaration.value, Type.i32); - initializeInStart = declaration.value.kind != NodeKind.LITERAL; - } else if (previousName == null) { - initializer = this.module.createI32(0); + if (element.hasConstantValue) { + if (type.isLongInteger) + initializer = this.module.createI64(element.constantIntegerValue.lo, element.constantIntegerValue.hi); + else if (type.kind == TypeKind.F32) + initializer = this.module.createF32(element.constantFloatValue); + else if (type.kind == TypeKind.F64) + initializer = this.module.createF64(element.constantFloatValue); + else if (type.isSmallInteger) { + if (type.isSignedInteger) { + const shift: i32 = type.smallIntegerShift; + initializer = this.module.createI32(element.constantIntegerValue.toI32() << shift >> shift); + } else + initializer = this.module.createI32(element.constantIntegerValue.toI32() & type.smallIntegerMask); + } else + initializer = this.module.createI32(element.constantIntegerValue.toI32()); initializeInStart = false; - } else { - initializer = this.module.createBinary(BinaryOp.AddI32, - this.module.createGetGlobal(previousName, BinaryenType.I32), - this.module.createI32(1) - ); - initializeInStart = true; - } + this.module.addGlobal(element.internalName, binaryenType, element.isMutable, initializer); + } else if (declaration) { + if (declaration.initializer) { + initializer = this.compileExpression(declaration.initializer, type); + initializeInStart = declaration.initializer.kind != NodeKind.LITERAL; // MVP doesn't support complex initializers + } else { + initializer = typeToBinaryenZero(this.module, type); + initializeInStart = false; + } + } else + throw new Error("unexpected missing declaration or constant value"); + const internalName: string = element.internalName; if (initializeInStart) { this.module.addGlobal(internalName, BinaryenType.I32, true, this.module.createI32(-1)); this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer)); } else - this.module.addGlobal(internalName, BinaryenType.I32, false, initializer); - - // export if applicable - if (declaration.parent && (declaration.parent).kind == NodeKind.ENUM && (declaration.parent).globalExportName != null) { - // TODO: WASM does not support exporting globals yet - // this.module.addExport(internalName, (declaration.parent).exportName); - } - return internalName; + this.module.addGlobal(internalName, BinaryenType.I32, element.isMutable, initializer); + return element.compiled = true; } - checkTypeArguments(typeParameters: TypeParameter[], typeArguments: Type[], reportNode: Node | null = null): bool { - if (typeParameters.length != typeArguments.length) { - if (reportNode) - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, (reportNode).range, typeParameters.length.toString(10), typeArguments.length.toString(10)); - return false; - } - // TODO: check class types, arrays - // TODO: check TypeParameter#extendsType - return true; + // enums + + compileEnumDeclaration(declaration: EnumDeclaration): void { + const element: Element | null = this.program.elements.get(declaration.internalName); + if (!element || element.kind != ElementKind.ENUM) + throw new Error("unexpected missing enum"); + this.compileEnum(element); } - compileFunction(declaration: FunctionDeclaration, typeArguments: Type[], inheritedTypeArgumentsMap: Map | null = null, reportNode: Node | null = null): void { - if (!declaration.statements) { - if (reportNode) - this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, (reportNode).range); + compileEnum(element: Enum): void { + if (element.compiled) return; - } - - if (!this.checkTypeArguments(declaration.typeParameters, typeArguments, reportNode)) // reports if requested - return; - - let internalName: string = declaration.internalName; - - // inherit type arguments, i.e. from class - const typeArgumentsMap: Map = new Map(); - if (inheritedTypeArgumentsMap) - for (let [key, val] of (>inheritedTypeArgumentsMap)) - typeArgumentsMap.set(key, val); - - // set (possibly override) type arguments for this specific call - let i: i32, k: i32 = typeArguments.length; - if (k) { - for (i = 0; i < k; ++i) - typeArgumentsMap.set(declaration.typeParameters[i].identifier.name, typeArguments[i]); - internalName += typeArgumentsToString(typeArguments); - } - - if (this.functions.has(internalName)) { - if (reportNode) - this.error(DiagnosticCode.Duplicate_function_implementation, (reportNode).range); - return; - } - - // resolve parameters - k = declaration.parameters.length; - const parameterNames: string[] = new Array(k); - const parameterTypes: Type[] = new Array(k); - for (i = 0; i < k; ++i) { - parameterNames[i] = declaration.parameters[i].identifier.name; - const typeNode: TypeNode | null = declaration.parameters[i].type; - if (typeNode) { - const type: Type | null = this.resolveType(typeNode, typeArgumentsMap, true); // reports - if (type) - parameterTypes[i] = type; - else - return; + element.compiled = true; + let previousInternalName: string | null = null; + for (let [key, val] of element.members) { + if (val.hasConstantValue) { + this.module.addGlobal(val.internalName, BinaryenType.I32, false, this.module.createI32(val.constantValue)); + } else if (val.declaration) { + const declaration: EnumValueDeclaration = val.declaration; + let initializer: BinaryenExpressionRef; + let initializeInStart: bool = false; + if (declaration.value) { + initializer = this.compileExpression(declaration.value, Type.i32); + initializeInStart = declaration.value.kind != NodeKind.LITERAL; // MVP doesn't support complex initializers + } else if (previousInternalName == null) { + initializer = this.module.createI32(0); + initializeInStart = false; + } else { + initializer = this.module.createBinary(BinaryOp.AddI32, + this.module.createGetGlobal(previousInternalName, BinaryenType.I32), + this.module.createI32(1) + ); + initializeInStart = true; + } + if (initializeInStart) { + this.module.addGlobal(val.internalName, BinaryenType.I32, true, this.module.createI32(-1)); + this.startFunctionBody.push(this.module.createSetGlobal(val.internalName, initializer)); + } else + this.module.addGlobal(val.internalName, BinaryenType.I32, false, initializer); } else - return; // TODO: infer type? (currently reported by parser) + throw new Error("unexpected missing declaration or constant value"); + previousInternalName = val.internalName; } + } - // resolve return type - const typeNode: TypeNode | null = declaration.returnType; - let returnType: Type; - if (typeNode) { - const type: Type | null = this.resolveType(typeNode, typeArgumentsMap, true); // reports - if (type) - returnType = type; - else - return; - } else - return; // TODO: infer type? (currently reported by parser) + // functions + + compileFunctionDeclaration(declaration: FunctionDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): void { + const internalName: string = declaration.internalName; + const element: Element | null = this.program.elements.get(internalName); + if (!element || element.kind != ElementKind.FUNCTION) + throw new Error("unexpected missing function"); + const resolvedTypeArguments: Type[] | null = this.program.resolveTypeArguments(declaration.typeParameters, typeArguments, contextualTypeArguments, alternativeReportNode); // reports + if (!resolvedTypeArguments) + return; + this.compileFunction(element, resolvedTypeArguments, contextualTypeArguments); + } + + compileFunction(template: FunctionTemplate, typeArguments: Type[], contextualTypeArguments: Map | null = null): void { + const instance: FunctionInstance | null = template.instantiate(typeArguments, contextualTypeArguments); + if (!instance || instance.compiled) + return; + + const declaration: FunctionDeclaration | null = template.declaration; + if (!declaration) // TODO: compile builtins + throw new Error("not implemented"); + + if (!declaration.statements) { + this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, declaration.identifier.range); + return; + } + instance.compiled = true; // compile statements - const functionType: FunctionType = new FunctionType(typeArguments, parameterTypes, returnType, parameterNames); - this.functions.set(internalName, functionType); - const previousFunction: FunctionType = this.currentFunction; - this.currentFunction = functionType; + const previousFunction: FunctionInstance = this.currentFunction; + this.currentFunction = instance; const stmts: BinaryenExpressionRef[] = this.compileStatements(declaration.statements); this.currentFunction = previousFunction; - // create function - const binaryenResultType: BinaryenType = typeToBinaryenType(returnType); - const binaryenParamTypes: BinaryenType[] = typesToBinaryenTypes(parameterTypes); + // create the function + let k: i32 = instance.parameters.length; + const binaryenResultType: BinaryenType = typeToBinaryenType(instance.returnType); + const binaryenParamTypes: BinaryenType[] = new Array(k); + const signatureNameParts: string[] = new Array(k + 1); + for (let i: i32 = 0; i < k; ++i) { + binaryenParamTypes[i] = typeToBinaryenType(instance.parameters[i].type); + signatureNameParts[i] = typeToSignatureNamePart(instance.parameters[i].type); + } + signatureNameParts[k] = typeToSignatureNamePart(instance.returnType); let binaryenTypeRef: BinaryenFunctionTypeRef = this.module.getFunctionTypeBySignature(binaryenResultType, binaryenParamTypes); if (!binaryenTypeRef) - binaryenTypeRef = this.module.addFunctionType(typesToSignatureName(parameterTypes, returnType), binaryenResultType, binaryenParamTypes); - this.module.addFunction(internalName, binaryenTypeRef, typesToBinaryenTypes(functionType.additionalLocals), this.module.createBlock(null, stmts, BinaryenType.None)); - - // export if applicable - if (declaration.globalExportName != null) - this.module.addExport(internalName, declaration.globalExportName); + binaryenTypeRef = this.module.addFunctionType(signatureNameParts.join(""), binaryenResultType, binaryenParamTypes); + const internalName: string = instance.internalName; + this.module.addFunction(internalName, binaryenTypeRef, typesToBinaryenTypes(instance.additionalLocals), this.module.createBlock(null, stmts, BinaryenType.None)); + if (instance.globalExportName != null) + this.module.addExport(internalName, instance.globalExportName); } - compileGlobals(statement: VariableStatement): void { - const declarations: VariableDeclaration[] = statement.declarations; - const isConst: bool = hasModifier(ModifierKind.CONST, statement.modifiers); - for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) - this.compileGlobal(declarations[i], isConst); - } + // namespaces - compileGlobal(declaration: VariableDeclaration, isConst: bool): void { - const type: Type | null = declaration.type ? this.resolveType(declaration.type) : null; // reports - if (!type) - return; - const internalName: string = declaration.internalName; - if (this.globals.has(internalName)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); - return; - } - const binaryenType: BinaryenType = typeToBinaryenType(type); - // TODO: WASM does not support complex initializers for globals yet, hence me such globals mutable and initialize in start - let initializer: BinaryenExportRef; - let initializeInStart: bool; - if (declaration.initializer) { - initializer = this.compileExpression(declaration.initializer, type); - initializeInStart = (declaration.initializer).kind != NodeKind.LITERAL; - } else { - initializer = typeToBinaryenZero(this.module, type); - initializeInStart = false; - } - if (initializeInStart) { - this.module.addGlobal(internalName, binaryenType, true, typeToBinaryenZero(this.module, type)); - this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer)); - } else - this.module.addGlobal(internalName, binaryenType, !isConst, initializer); - this.globals.set(internalName, type); - - // export if applicable - if (declaration.globalExportName != null) { - // TODO: WASM does not support exporting globals yet - // this.module.addExport(internalName, declaration.exportName); - } - } - - compileNamespace(declaration: NamespaceDeclaration): void { + compileNamespaceDeclaration(declaration: NamespaceDeclaration): void { const members: Statement[] = declaration.members; const noTreeShaking: bool = this.options.noTreeShaking; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { @@ -429,27 +433,27 @@ export class Compiler extends DiagnosticEmitter { case NodeKind.CLASS: if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) && !(member).typeParameters.length) - this.compileClass(member, []); + this.compileClassDeclaration(member, []); break; case NodeKind.ENUM: if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) - this.compileEnum(member); + this.compileEnumDeclaration(member); break; case NodeKind.FUNCTION: if ((noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) && !(member).typeParameters.length) - this.compileFunction(member, []); + this.compileFunctionDeclaration(member, []); break; case NodeKind.NAMESPACE: if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) - this.compileNamespace(member); + this.compileNamespaceDeclaration(member); break; case NodeKind.VARIABLE: if (noTreeShaking || hasModifier(ModifierKind.EXPORT, (member).modifiers)) - this.compileGlobals(member); + this.compileVariableStatement(member); break; default: @@ -459,46 +463,89 @@ export class Compiler extends DiagnosticEmitter { throw new Error("not implemented"); } - compileExports(statement: ExportStatement): void { - const members: ExportMember[] = statement.members; - const normalizedPath: string | null = statement.path ? normalizePath((statement.path).value) : statement.range.source.normalizedPath; - for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) - this.compileExport(members[i], normalizedPath); + compileNamespace(ns: Namespace): void { + const noTreeShaking: bool = this.options.noTreeShaking; + for (let [name, element] of ns.members) { + switch (element.kind) { + + case ElementKind.CLASS: + if ((noTreeShaking || (element).isExport) && !(element).isGeneric) + this.compileClass(element, []); + break; + + case ElementKind.ENUM: + this.compileEnum(element); + break; + + case ElementKind.FUNCTION: + if ((noTreeShaking || (element).isExport) && !(element).isGeneric) + this.compileFunction(element, []); + break; + + case ElementKind.GLOBAL: + this.compileGlobal(element); + break; + + case ElementKind.NAMESPACE: + this.compileNamespace(element); + break; + } + } } - compileExport(member: ExportMember, normalizedPath: string): void { - const internalExportName: string = normalizedPath + "/" + member.identifier.name; - const declaration: DeclarationStatement | null = this.program.exports.get(internalExportName); - if (declaration) { - switch ((declaration).kind) { + // exports - case NodeKind.CLASS: - if (!(declaration).typeParameters.length) - this.compileClass(declaration, []); + compileExportStatement(statement: ExportStatement): void { + const members: ExportMember[] = statement.members; + 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 element: Element | null = this.program.exports.get(internalExportName); + if (!element) + throw new Error("unexpected missing element"); + switch (element.kind) { + + case ElementKind.CLASS: + if (!(element).isGeneric) + this.compileClass(element, []); break; - case NodeKind.ENUM: - this.compileEnum(declaration); + case ElementKind.ENUM: + this.compileEnum(element); break; - case NodeKind.FUNCTION: - if (!(declaration).typeParameters.length) - this.compileFunction(declaration, []); + case ElementKind.FUNCTION: + if (!(element).isGeneric) + this.compileFunction(element, []); break; - case NodeKind.NAMESPACE: - this.compileNamespace(declaration); + case ElementKind.GLOBAL: + this.compileGlobal(element); break; - case NodeKind.VARIABLEDECLARATION: - this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, (declaration.parent).modifiers)); + case ElementKind.NAMESPACE: + this.compileNamespace(element); break; - - default: - throw new Error("unexpected export declaration kind"); } - } else - throw new Error("unexpected missing declaration"); + } + } + + // classes + + compileClassDeclaration(declaration: ClassDeclaration, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): void { + const internalName: string = declaration.internalName; + const element: Element | null = this.program.elements.get(internalName); + if (!element || element.kind != ElementKind.CLASS) + throw new Error("unexpected missing class"); + const resolvedTypeArguments: Type[] | null = this.program.resolveTypeArguments(declaration.typeParameters, typeArguments, contextualTypeArguments, alternativeReportNode); // reports + if (!resolvedTypeArguments) + return; + this.compileClass(element, resolvedTypeArguments, contextualTypeArguments); + } + + compileClass(cls: ClassTemplate, typeArguments: Type[], contextualTypeArguments: Map | null = null) { + throw new Error("not implemented"); } // memory @@ -516,41 +563,7 @@ export class Compiler extends DiagnosticEmitter { // types - resolveType(node: TypeNode, typeArgumentsMap: Map | null = null, reportNotFound: bool = true): Type | null { - - // resolve parameters - const k: i32 = node.parameters.length; - const paramTypes: Type[] = new Array(k); - for (let i: i32 = 0; i < k; ++i) { - const paramType: Type | null = this.resolveType(node.parameters[i], typeArgumentsMap, reportNotFound); - if (!paramType) - return null; - paramTypes[i] = paramType; - } - - let globalName: string = node.identifier.name; - if (k) // not a placeholder - globalName += typeArgumentsToString(paramTypes); - else if (typeArgumentsMap && (>typeArgumentsMap).has(globalName)) // possibly a placeholder - return (>typeArgumentsMap).get(globalName); - - const types: Map = this.program.types; - - // resolve local type - const localName: string = node.range.source.normalizedPath + "/" + globalName; - if (types.has(localName)) - return types.get(localName); - - // resolve global type - if (types.has(globalName)) - return types.get(globalName); - - if (reportNotFound) - this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName); - return null; - } - - resolveExpressionType(expression: Expression, contextualType: Type): Type { + determineExpressionType(expression: Expression, contextualType: Type): Type { const previousType: Type = this.currentType; const previousNoEmit: bool = this.module.noEmit; this.module.noEmit = true; @@ -561,50 +574,6 @@ export class Compiler extends DiagnosticEmitter { return type; } - resolveClassType(expression: Expression, typeArguments: TypeNode[], typeArgumentsMap: Map | null = null, create: bool = false): ClassType | null { - if (expression.kind == NodeKind.THIS) { - if (this.currentClass) - return this.currentClass; - this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); - return null; - } - if (expression.kind == NodeKind.IDENTIFIER) { - throw new Error("not implemented"); - } - if (expression.kind == NodeKind.ELEMENTACCESS) { - throw new Error("not implemented"); - } - if (expression.kind == NodeKind.PROPERTYACCESS) { - throw new Error("not implemented"); - } - return null; - } - - resolveFunctionType(expression: Expression, typeArguments: TypeNode[], typeArgumentsMap: Map | null = null, create: bool = false): FunctionType | null { - const k: i32 = typeArguments.length; - const resolvedTypeArguments: Type[] = new Array(k); - for (let i: i32 = 0; i < k; ++i) { - const type: Type | null = this.resolveType(typeArguments[i], typeArgumentsMap, true); // reports - if (!type) - return null; - resolvedTypeArguments[i] = type; - } - if (expression.kind == NodeKind.IDENTIFIER) { - let globalName: string = (expression).name; - if (k) - globalName += typeArgumentsToString(resolvedTypeArguments); - const localName = expression.range.source.normalizedPath + "/" + globalName; - if (this.functions.has(localName)) - return this.functions.get(localName); - if (this.functions.has(globalName)) - return this.functions.get(globalName); - this.error(DiagnosticCode.Cannot_find_name_0, expression.range, globalName); - } else { - throw new Error("not implemented"); - } - return null; - } - // statements compileStatement(statement: Statement): BinaryenExpressionRef { @@ -753,12 +722,12 @@ export class Compiler extends DiagnosticEmitter { this.disallowContinue = true; // introduce a local for evaluating the condition (exactly once) - const localIndex: i32 = this.currentFunction.addLocal(Type.i32); + const local: Local = this.currentFunction.addLocal(Type.i32); let i: i32, k: i32 = statement.cases.length; // prepend initializer to inner block const breaks: BinaryenExpressionRef[] = new Array(1 + k); - breaks[0] = this.module.createSetLocal(localIndex, this.compileExpression(statement.expression, Type.i32)); // initializer + breaks[0] = this.module.createSetLocal(local.index, this.compileExpression(statement.expression, Type.i32)); // initializer // make one br_if per (possibly dynamic) labeled case // TODO: take advantage of br_table where labels are known to be (sequential) constant (ideally Binaryen's optimizer would) @@ -768,7 +737,7 @@ export class Compiler extends DiagnosticEmitter { const case_: SwitchCase = statement.cases[i]; if (case_.label) { breaks[breakIndex++] = this.module.createBreak("case" + i.toString(10) + "$" + context, this.module.createBinary(BinaryOp.EqI32, - this.module.createGetLocal(localIndex, BinaryenType.I32), + this.module.createGetLocal(local.index, BinaryenType.I32), this.compileExpression(case_.label, Type.i32) )); } else @@ -812,19 +781,22 @@ export class Compiler extends DiagnosticEmitter { } compileVariableStatement(statement: VariableStatement): BinaryenExpressionRef { + const declarations: VariableDeclaration[] = statement.declarations; + // top-level variables become globals if (this.currentFunction == this.startFunction) { - this.compileGlobals(statement); + const isConst: bool = hasModifier(ModifierKind.CONST, statement.modifiers); + for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) + this.compileGlobalDeclaration(declarations[i], isConst); return this.module.createNop(); } // other variables become locals - const declarations: VariableDeclaration[] = statement.declarations; const initializers: BinaryenExpressionRef[] = new Array(); for (let i: i32 = 0, k = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; if (declaration.type) { const name: string = declaration.identifier.name; - const type: Type | null = this.resolveType(declaration.type, this.currentFunction.typeArgumentsMap, true); // reports + const type: Type | null = this.program.resolveType(declaration.type, this.currentFunction.contextualTypeArguments, true); // reports if (type) { if (this.currentFunction.locals.has(name)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); // recoverable @@ -1081,7 +1053,7 @@ export class Compiler extends DiagnosticEmitter { } compileAssertionExpression(expression: AssertionExpression, contextualType: Type): BinaryenExpressionRef { - const toType: Type | null = this.resolveType(expression.toType, this.currentFunction.typeArgumentsMap); // reports + const toType: Type | null = this.program.resolveType(expression.toType, this.currentFunction.contextualTypeArguments, true); // reports if (toType && toType != contextualType) { const expr: BinaryenExpressionRef = this.compileExpression(expression.expression, toType); return this.convertExpression(expr, this.currentType, toType); @@ -1309,48 +1281,44 @@ export class Compiler extends DiagnosticEmitter { } compileAssignment(expression: Expression, valueExpression: Expression, contextualType: Type): BinaryenExpressionRef { - this.currentType = this.resolveExpressionType(expression, contextualType); + this.currentType = this.determineExpressionType(expression, contextualType); return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, this.currentType), contextualType != Type.void); } compileAssignmentWithValue(expression: Expression, valueWithCorrectType: BinaryenExpressionRef, tee: bool = false): BinaryenExpressionRef { - if (expression.kind == NodeKind.IDENTIFIER) { - const name: string = (expression).name; - const locals: Map = this.currentFunction.locals; - if (locals.has(name)) { - const local: LocalType = locals.get(name); - if (tee) { - this.currentType = local.type; - return this.module.createTeeLocal(local.index, valueWithCorrectType); - } - this.currentType = Type.void; - return this.module.createSetLocal(local.index, valueWithCorrectType); - } - const globals: Map = this.globals; - if (globals.has(name)) { - const globalType: Type = globals.get(name); - if (tee) { - const globalBinaryenType: BinaryenType = typeToBinaryenType(globalType); - this.currentType = globalType; - return this.module.createBlock(null, [ // teeGlobal - this.module.createSetGlobal(name, valueWithCorrectType), - this.module.createGetGlobal(name, globalBinaryenType) - ], globalBinaryenType); - } - this.currentType = Type.void; - return this.module.createSetGlobal(name, valueWithCorrectType); + const element: Element | null = this.program.resolveElement(expression, this.currentFunction); + if (!element) + return this.module.createUnreachable(); + + if (element.kind == ElementKind.LOCAL) { + if (tee) { + this.currentType = (element).type; + return this.module.createTeeLocal((element).index, valueWithCorrectType); } + return this.module.createSetLocal((element).index, valueWithCorrectType); } + + if (element.kind == ElementKind.GLOBAL && (element).type) { + if (tee) { + const globalBinaryenType: BinaryenType = typeToBinaryenType((element).type); + this.currentType = (element).type; + return this.module.createBlock(null, [ // teeGlobal + this.module.createSetGlobal((element).internalName, valueWithCorrectType), + this.module.createGetGlobal((element).internalName, globalBinaryenType) + ], globalBinaryenType); + } + return this.module.createSetGlobal((element).internalName, valueWithCorrectType); + } + + // TODO: fields, setters + throw new Error("not implemented"); } compileCallExpression(expression: CallExpression, contextualType: Type): BinaryenExpressionRef { - const functionType: FunctionType | null = this.resolveFunctionType(expression.expression, expression.typeArguments, this.currentFunction.typeArgumentsMap, true); - if (!functionType) + const element: Element | null = this.program.resolveElement(expression, this.currentFunction); + if (!element || element.kind != ElementKind.FUNCTION) return this.module.createUnreachable(); - // TODO: compile if not yet compiled - // TODO: validate parameters and call - // this.module.createCall(functionType.ref, operands, typeToBinaryenType(functionType.returnType)); throw new Error("not implemented"); } @@ -1384,8 +1352,8 @@ export class Compiler extends DiagnosticEmitter { // this } else if (expression.kind == NodeKind.THIS) { - if (/* TODO: this.currentFunction.isInstance &&*/ this.currentClass) { - this.currentType = this.currentClass.type; + if (this.currentFunction.instanceMethodOf) { + this.currentType = this.currentFunction.instanceMethodOf.type; return this.module.createGetLocal(0, typeToBinaryenType(this.currentType)); } this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); @@ -1393,42 +1361,50 @@ export class Compiler extends DiagnosticEmitter { return this.module.createUnreachable(); } - const globalName: string = expression.name; // same as local variable name + const element: Element | null = this.program.resolveElement(expression, this.currentFunction); // reports + if (!element) { + if (expression.kind == NodeKind.IDENTIFIER) { - // local variable - const locals: Map = this.currentFunction.locals; - if (locals.has(globalName)) { - const local: LocalType = locals.get(globalName); - this.currentType = local.type; - return this.module.createGetLocal(local.index, typeToBinaryenType(this.currentType)); - } + // NaN + if ((expression).name == "NaN") + if (this.currentType.kind == TypeKind.F32) + return this.module.createF32(NaN); + else { + this.currentType = Type.f64; + return this.module.createF64(NaN); + } - // global in local file - const localName: string = expression.range.source.normalizedPath + "/" + globalName; - let determinedName: string = localName; - if (this.program.names.has(localName)) { - const declaration: DeclarationStatement = this.program.names.get(localName); - if (declaration.kind == NodeKind.VARIABLEDECLARATION) { - if (!this.globals.has(declaration.internalName)) - this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, declaration.modifiers)); - } - - // global across files - } else if (this.program.names.has(globalName)) { - const declaration: DeclarationStatement = this.program.names.get(globalName); - if (declaration.kind == NodeKind.VARIABLEDECLARATION) { - if (!this.globals.has(declaration.internalName)) - this.compileGlobal(declaration, hasModifier(ModifierKind.CONST, declaration.modifiers)); - determinedName = globalName; + // Infinity + if ((expression).name == "Infinity") + if (this.currentType.kind == TypeKind.F32) + return this.module.createF32(Infinity); + else { + this.currentType = Type.f64; + return this.module.createF64(Infinity); + } } + return this.module.createUnreachable(); } - const globals: Map = this.globals; - if (globals.has(determinedName)) { - this.currentType = globals.get(determinedName); - return this.module.createGetGlobal(determinedName, typeToBinaryenType(this.currentType)); - } - this.error(DiagnosticCode.Cannot_find_name_0, expression.range, determinedName); + // local + if (element.kind == ElementKind.LOCAL) + return this.module.createGetLocal((element).index, typeToBinaryenType(this.currentType = (element).type)); + + // global + if (element.kind == ElementKind.GLOBAL) + return this.compileGlobal(element) // reports + ? this.module.createGetGlobal((element).internalName, typeToBinaryenType(this.currentType = (element).type)) + : this.module.createUnreachable(); + + // field + // if (element.kind == ElementKind.FIELD) + // throw new Error("not implemented"); + + // getter + if (element.kind == ElementKind.FUNCTION && (element).isGetter) + throw new Error("not implemented"); + + this.error(DiagnosticCode.Operation_not_supported, expression.range); return this.module.createUnreachable(); } diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..e9f02e73 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,8 @@ +// internal naming scheme + +export const PATH_DELIMITER: string = "/"; +export const PARENT_SUBST: string = ".."; +export const GETTER_PREFIX: string = "get:"; +export const SETTER_PREFIX: string = "set:"; +export const INSTANCE_DELIMITER: string = "#"; +export const STATIC_DELIMITER: string = "."; diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index f80e053c..a5fd8466 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -3,6 +3,7 @@ export enum DiagnosticCode { Conversion_from_type_0_to_1_requires_an_explicit_cast = 100, Basic_type_0_cannot_be_nullable = 101, + Operation_not_supported = 102, Unterminated_string_literal = 1002, Identifier_expected = 1003, _0_expected = 1005, @@ -32,6 +33,7 @@ export enum DiagnosticCode { Unexpected_end_of_text = 1126, Invalid_character = 1127, _case_or_default_expected = 1130, + Type_argument_expected = 1140, String_literal_expected = 1141, Line_break_not_permitted_here = 1142, Declaration_expected = 1146, @@ -50,8 +52,10 @@ export enum DiagnosticCode { Type_0_is_not_assignable_to_type_1 = 2322, _this_cannot_be_referenced_in_current_location = 2332, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, + The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access = 2364, Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391, Duplicate_function_implementation = 2393, + The_target_of_an_assignment_must_be_a_variable_or_a_property_access = 2541, Expected_0_type_arguments_but_got_1 = 2558, File_0_not_found = 6054 } @@ -60,6 +64,7 @@ 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 102: return "Operation not supported."; case 1002: return "Unterminated string literal."; case 1003: return "Identifier expected."; case 1005: return "'{0}' expected."; @@ -89,6 +94,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1126: return "Unexpected end of text."; case 1127: return "Invalid character."; case 1130: return "'case' or 'default' expected."; + case 1140: return "Type argument expected."; case 1141: return "String literal expected."; case 1142: return "Line break not permitted here."; case 1146: return "Declaration expected."; @@ -107,8 +113,10 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2322: return "Type '{0}' is not assignable to type '{1}'."; case 2332: return "'this' cannot be referenced in current location."; case 2357: return "The operand of an increment or decrement operator must be a variable or a property access."; + case 2364: return "The left-hand side of an assignment expression must be a variable or a property access."; case 2391: return "Function implementation is missing or not immediately following the declaration."; case 2393: return "Duplicate function implementation."; + case 2541: return "The target of an assignment must be a variable or a property access."; case 2558: return "Expected {0} type arguments, but got {1}."; case 6054: return "File '{0}' not found."; default: return ""; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index a98e2cd3..c44ba842 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -1,6 +1,7 @@ { "Conversion from type '{0}' to '{1}' requires an explicit cast.": 100, "Basic type '{0}' cannot be nullable.": 101, + "Operation not supported.": 102, "Unterminated string literal.": 1002, "Identifier expected.": 1003, @@ -31,6 +32,7 @@ "Unexpected end of text.": 1126, "Invalid character.": 1127, "'case' or 'default' expected.": 1130, + "Type argument expected.": 1140, "String literal expected.": 1141, "Line break not permitted here.": 1142, "Declaration expected.": 1146, @@ -50,8 +52,10 @@ "Type '{0}' is not assignable to type '{1}'.": 2322, "'this' cannot be referenced in current location.": 2332, "The operand of an increment or decrement operator must be a variable or a property access.": 2357, + "The left-hand side of an assignment expression must be a variable or a property access.": 2364, "Function implementation is missing or not immediately following the declaration.": 2391, "Duplicate function implementation.": 2393, + "The target of an assignment must be a variable or a property access.": 2541, "Expected {0} type arguments, but got {1}.": 2558, "File '{0}' not found.": 6054 diff --git a/src/parser.ts b/src/parser.ts index 77844e22..03472e0b 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -462,13 +462,13 @@ export class Parser extends DiagnosticEmitter { // Identifier ('extends' Type)? if (tn.next() == Token.IDENTIFIER) { const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); - let extendsName: TypeNode | null = null; + let extendsType: TypeNode | null = null; if (tn.skip(Token.EXTENDS)) { - extendsName = this.parseType(tn); - if (!extendsName) + extendsType = this.parseType(tn); + if (!extendsType) return null; } - return Statement.createTypeParameter(identifier, extendsName, Range.join(identifier.range, tn.range())); + return Statement.createTypeParameter(identifier, extendsType, Range.join(identifier.range, tn.range())); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; diff --git a/src/program.ts b/src/program.ts index d3603746..2c9ae165 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,15 +1,27 @@ import { Target } from "./compiler"; +import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER } from "./constants"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; -import { Type } from "./types"; +import { Type, typesToString } from "./types"; +import { I64 } from "./util"; import { ModifierKind, Node, NodeKind, Source, + Range, + + TypeNode, + Expression, + IdentifierExpression, + LiteralExpression, + LiteralKind, + PropertyAccessExpression, + StringLiteralExpression, ClassDeclaration, DeclarationStatement, + DecoratorStatement, EnumDeclaration, EnumValueDeclaration, ExportMember, @@ -20,8 +32,11 @@ import { ImportStatement, InterfaceDeclaration, MethodDeclaration, + Modifier, NamespaceDeclaration, Statement, + TypeParameter, + VariableLikeDeclarationStatement, VariableDeclaration, VariableStatement, @@ -41,29 +56,32 @@ class QueuedImport { declaration: ImportDeclaration; } -const typesStub: Map = new Map(); +const reusableTypesStub: Map = new Map(); export class Program extends DiagnosticEmitter { + /** Array of source files. */ sources: Source[]; + /** Diagnostic offset used where sequentially obtaining the next diagnostic. */ diagnosticsOffset: i32 = 0; - target: Target = Target.WASM32; - - /** Map of internal names to declarations. */ - names: Map = new Map(); - /** Separate map of internal type names to declarations. */ - types: Map = typesStub; - /** Separate map of internal export names to declarations. */ - exports: Map = new Map(); // not global exports + /** WASM target. */ + target: Target = Target.WASM32; // set on initialization + /** Elements by internal name. */ + elements: Map = new Map(); + /** Types by internal name. */ + types: Map = reusableTypesStub; + /** Exports of individual files by internal name. Not global exports. */ + exports: Map = new Map(); constructor(diagnostics: DiagnosticMessage[] | null = null) { super(diagnostics); this.sources = new Array(); } - initialize(target: Target): void { + /** Initializes the program and its elements prior to compilation. */ + initialize(target: Target = Target.WASM32): void { this.target = target; - this.types = new Map([ + this.types = new Map([ // replaces typesStub ["i8", Type.i8], ["i16", Type.i16], ["i32", Type.i32], @@ -127,9 +145,7 @@ export class Program extends DiagnosticEmitter { } // at this point queued exports should be resolvable - // export { add } - // export { sub } from "./other" - for (let [exportName, queuedExport] of queuedExports) { + for (let [exportName, queuedExport] of queuedExports) { // all file-level exports if (queuedExport.isForeign) { const seen: Set = new Set(); while (queuedExports.has(queuedExport.referencedName)) { @@ -139,18 +155,20 @@ export class Program extends DiagnosticEmitter { seen.add(queuedExport); } if (this.exports.has(queuedExport.referencedName)) { - const declaration: DeclarationStatement = this.exports.get(queuedExport.referencedName); - this.addExport(exportName, declaration); + const element: Element = this.exports.get(queuedExport.referencedName); + if (!this.exports.has(exportName)) + this.exports.set(exportName, element); if (queuedExport.member.range.source.isEntry) - declaration.globalExportName = queuedExport.member.externalIdentifier.name; + element.globalExportName = queuedExport.member.externalIdentifier.name; } else this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.referencedName); } else /* local */ { - if (this.names.has(queuedExport.referencedName)) { - const declaration: DeclarationStatement = this.names.get(queuedExport.referencedName); - this.addExport(exportName, declaration); + 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) - declaration.globalExportName = queuedExport.member.externalIdentifier.name; + element.globalExportName = queuedExport.member.externalIdentifier.name; } else this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.externalIdentifier.range, queuedExport.referencedName); } @@ -170,13 +188,11 @@ export class Program extends DiagnosticEmitter { seen.add(queuedExport); } if (this.exports.has(importName)) { - if (this.names.has(internalName)) + if (this.elements.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, queuedImport.declaration.identifier.range, internalName); else { - const declaration: DeclarationStatement = this.exports.get(importName); - this.names.set(internalName, declaration); - // TODO: also mirror (non-private) member names? - // wouldn't it be better to look up members based on their parent? + 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); @@ -185,20 +201,28 @@ export class Program extends DiagnosticEmitter { private initializeClass(declaration: ClassDeclaration): void { const internalName: string = declaration.internalName; - this.addName(internalName, declaration); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(/* same as */internalName, declaration); - const members: DeclarationStatement[] = declaration.members; - for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { - const statement: Statement = members[j]; - switch (statement.kind) { + const template: ClassTemplate = new ClassTemplate(this, internalName, declaration); + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.elements.set(internalName, template); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (this.exports.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.exports.set(internalName, template); + } + const memberDeclarations: DeclarationStatement[] = declaration.members; + for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) { + const memberDeclaration: DeclarationStatement = memberDeclarations[j]; + switch (memberDeclaration.kind) { case NodeKind.FIELD: - this.initializeField(statement); + this.initializeField(memberDeclaration, template); break; case NodeKind.METHOD: - this.initializeMethod(statement); + this.initializeMethod(memberDeclaration, template); break; default: @@ -207,42 +231,103 @@ export class Program extends DiagnosticEmitter { } } - private initializeField(declaration: FieldDeclaration): void { - this.addName(declaration.internalName, declaration); + private initializeField(declaration: FieldDeclaration, template: ClassTemplate): void { + const name: string = declaration.identifier.name; + if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) { + const internalName: string = declaration.internalName; + if (template.members.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + const global: Global = new Global(this, internalName, declaration, null); + template.members.set(name, global); + } + } else { + if (template.fieldDeclarations.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); + else + template.fieldDeclarations.set(name, declaration); + } + } + + private initializeMethod(declaration: MethodDeclaration, template: ClassTemplate): void { + let name: string = declaration.identifier.name; + if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) { + const internalName: string = declaration.internalName; + if (template.members.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + const func: FunctionTemplate = new FunctionTemplate(this, internalName, declaration); + let modifiers: Modifier[] | null; + if (modifiers = declaration.modifiers) { + for (let i: i32 = 0, k: i32 = modifiers.length; i < k; ++i) { + const modifier: Modifier = modifiers[i]; + if (modifier.modifierKind == ModifierKind.GET) { + name = GETTER_PREFIX + name; + break; + } else if (modifier.modifierKind == ModifierKind.SET) { + name = SETTER_PREFIX + name; + break; + } + } + } + template.members.set(name, func); + } + } else { + if (template.methodDeclarations.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, name); + else + template.methodDeclarations.set(name, declaration); + } } private initializeEnum(declaration: EnumDeclaration): void { const internalName: string = declaration.internalName; - this.addName(internalName, declaration); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(/* same as */internalName, declaration); - const members: EnumValueDeclaration[] = declaration.members; - for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) - this.initializeEnumValue(members[i]); + const enm: Enum = new Enum(this, internalName, declaration); + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + this.elements.set(internalName, enm); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (this.exports.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.exports.set(internalName, enm); + } + const members: EnumValueDeclaration[] = declaration.members; + for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) + this.initializeEnumValue(members[i], enm); + } } - private initializeEnumValue(declaration: EnumValueDeclaration): void { - this.addName(declaration.internalName, declaration); + private initializeEnumValue(declaration: EnumValueDeclaration, enm: Enum): void { + const name: string = declaration.identifier.name; + const internalName: string = declaration.internalName; + if (enm.members.has(name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + const value: EnumValue = new EnumValue(enm, this, internalName, declaration); + enm.members.set(name, value); + } } private initializeExports(statement: ExportStatement, queuedExports: Map): void { const members: ExportMember[] = statement.members; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) - this.initializeExport(members[i], statement.normalizedPath, queuedExports); + this.initializeExport(members[i], statement.internalPath, queuedExports); } - private initializeExport(member: ExportMember, normalizedPath: string | null, queuedExports: Map): void { - const exportName: string = member.range.source.normalizedPath + "/" + member.externalIdentifier.name; + 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.Duplicate_identifier_0, member.externalIdentifier.range, exportName); else { const queuedExport: QueuedExport = new QueuedExport(); - if (normalizedPath == null) { + if (internalPath == null) { queuedExport.isForeign = false; - queuedExport.referencedName = member.range.source.normalizedPath + "/" + member.identifier.name; + queuedExport.referencedName = member.range.source.internalPath + PATH_DELIMITER + member.identifier.name; } else { queuedExport.isForeign = true; - queuedExport.referencedName = (normalizedPath) + "/" + member.identifier.name; + queuedExport.referencedName = (internalPath) + PATH_DELIMITER + member.identifier.name; } queuedExport.member = member; queuedExports.set(exportName, queuedExport); @@ -251,21 +336,30 @@ export class Program extends DiagnosticEmitter { private initializeFunction(declaration: FunctionDeclaration): void { const internalName: string = declaration.internalName; - this.addName(internalName, declaration); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(/* same as */internalName, declaration); + const template: FunctionTemplate = new FunctionTemplate(this, internalName, declaration); + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + this.elements.set(internalName, template); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (this.exports.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.exports.set(internalName, template); + } + } } private initializeImports(statement: ImportStatement, queuedExports: Map, queuedImports: QueuedImport[]): void { const members: ImportDeclaration[] = statement.declarations; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { const declaration: ImportDeclaration = members[i]; - this.initializeImport(declaration, statement.normalizedPath, queuedExports, queuedImports); + this.initializeImport(declaration, statement.internalPath, queuedExports, queuedImports); } } - private initializeImport(declaration: ImportDeclaration, normalizedPath: string, queuedExports: Map, queuedImports: QueuedImport[]): void { - const importName: string = normalizedPath + "/" + declaration.externalIdentifier.name; + 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)) { @@ -277,10 +371,10 @@ export class Program extends DiagnosticEmitter { } const internalName: string = declaration.internalName; if (this.exports.has(resolvedImportName)) { // resolvable right away - if (this.names.has(internalName)) + if (this.elements.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else - this.names.set(internalName, this.exports.get(resolvedImportName)); + this.elements.set(internalName, this.exports.get(resolvedImportName)); } else { // points to yet unresolved export const queuedImport: QueuedImport = new QueuedImport(); queuedImport.internalName = internalName; @@ -292,20 +386,28 @@ export class Program extends DiagnosticEmitter { private initializeInterface(declaration: InterfaceDeclaration): void { const internalName: string = declaration.internalName; - this.addName(internalName, declaration); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(/* same as */internalName, declaration); - const members: Statement[] = declaration.members; - for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { - const statement: Statement = members[j]; - switch (statement.kind) { + const template: InterfaceTemplate = new InterfaceTemplate(this, internalName, declaration); + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.elements.set(internalName, template); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (this.exports.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.exports.set(internalName, template); + } + const memberDeclarations: DeclarationStatement[] = declaration.members; + for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) { + const memberDeclaration: DeclarationStatement = memberDeclarations[j]; + switch (memberDeclaration.kind) { case NodeKind.FIELD: - this.initializeField(statement); + this.initializeField(memberDeclaration, template); break; case NodeKind.METHOD: - this.initializeMethod(statement); + this.initializeMethod(memberDeclaration, template); break; default: @@ -314,15 +416,20 @@ export class Program extends DiagnosticEmitter { } } - private initializeMethod(declaration: MethodDeclaration): void { - this.addName(declaration.internalName, declaration); - } - private initializeNamespace(declaration: NamespaceDeclaration): void { const internalName: string = declaration.internalName; - this.addName(internalName, declaration); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) - this.addExport(/* same as */internalName, declaration); + const ns: Namespace = new Namespace(this, internalName, declaration); + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + this.elements.set(internalName, ns); + if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (this.exports.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.exports.set(internalName, ns); + } + } const members: Statement[] = declaration.members; for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { const statement: Statement = members[j]; @@ -364,26 +471,484 @@ export class Program extends DiagnosticEmitter { for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; const internalName: string = declaration.internalName; - this.addName(internalName, declaration); - if (isExport) - this.addExport(/* same as */internalName, declaration); + const global: Global = new Global(this, internalName, declaration, null); + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else { + this.elements.set(internalName, global); + if (isExport) { + if (this.exports.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.exports.set(internalName, global); + } + } } } - private addName(internalName: string, declaration: DeclarationStatement): void { - if (this.names.has(internalName)) - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); // recoverable - else - this.names.set(internalName, declaration); + resolveType(node: TypeNode, contextualTypeArguments: Map | null = null, reportNotFound: bool = true): Type | null { + + // resolve parameters + const k: i32 = node.parameters.length; + const paramTypes: Type[] = new Array(k); + for (let i: i32 = 0; i < k; ++i) { + const paramType: Type | null = this.resolveType(node.parameters[i], contextualTypeArguments, reportNotFound); + if (!paramType) + return null; + paramTypes[i] = paramType; + } + + let globalName: string = node.identifier.name; + if (k) // can't be a placeholder if it has parameters + globalName += typesToString(paramTypes); + else if (contextualTypeArguments) { + const placeholderType: Type | null = contextualTypeArguments.get(globalName); + if (placeholderType) + return placeholderType; + } + + let type: Type | null; + + // check file-global type + if (type = this.types.get(node.range.source.internalPath + PATH_DELIMITER + globalName)) + return type; + + // check program-global type + if (type = this.types.get(globalName)) + return type; + + if (reportNotFound) + this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName); + + return null; } - private addExport(exportName: string, declaration: DeclarationStatement): void { - if (this.exports.has(exportName)) - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, exportName); // recoverable - else { - this.exports.set(exportName, declaration); - if (declaration.range.source.isEntry) - declaration.globalExportName = declaration.identifier.name; + resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Type[] | null { + const parameterCount: i32 = typeParameters.length; + const argumentCount: i32 = typeArgumentNodes.length; + if (parameterCount != argumentCount) { + if (argumentCount) + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, Range.join(typeArgumentNodes[0].range, typeArgumentNodes[argumentCount - 1].range), parameterCount.toString(10), argumentCount.toString(10)); + else if (alternativeReportNode) + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, alternativeReportNode.range.atEnd, parameterCount.toString(10), "0"); + return null; + } + const typeArguments: Type[] = new Array(parameterCount); + for (let i: i32 = 0; i < parameterCount; ++i) { + const type: Type | null = this.resolveType(typeArgumentNodes[i], contextualTypeArguments, true); // reports + if (!type) + return null; + // TODO: check extendsType + typeArguments[i] = type; + } + return typeArguments; + } + + resolveElement(expression: Expression, contextualFunction: FunctionInstance): Element | null { + + // this + if (expression.kind == NodeKind.THIS) { + if (contextualFunction.instanceMethodOf) + return contextualFunction.instanceMethodOf.template; + this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); + return null; + } + + // local or global name + if (expression.kind == NodeKind.IDENTIFIER) { + const name: string = (expression).name; + const local: Local | null = contextualFunction.locals.get(name); + if (local) + return local; + const fileGlobalElement: Element | null = this.elements.get(expression.range.source.internalPath + PATH_DELIMITER + name); + if (fileGlobalElement) + return fileGlobalElement; + const programGlobalElement: Element | null = this.elements.get(name); + if (programGlobalElement) + return programGlobalElement; + return null; + + // static or instance property (incl. enum values) or method + } else if (expression.kind == NodeKind.PROPERTYACCESS) { + const target: Element | null = this.resolveElement((expression).expression, contextualFunction); + if (!target) + return null; + switch (target.kind) { + case ElementKind.CLASS: + case ElementKind.ENUM: + case ElementKind.NAMESPACE: + } + // TODO + } + + return null; + } +} + +function checkGlobalDecorator(decorators: DecoratorStatement[]): string | null { + for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { + const decorator: DecoratorStatement = decorators[i]; + const expression: Expression = decorator.expression; + const args: Expression[] = decorator.arguments; + if (expression.kind == NodeKind.IDENTIFIER && args.length <= 1 && (expression).name == "global") { + if (args.length) { + const firstArg: Expression = args[0]; + if (firstArg.kind == NodeKind.LITERAL && (firstArg).literalKind == LiteralKind.STRING) + return (firstArg).value; + } else + return ""; // instead inherits declaration identifier + } + } + return null; +} + +export enum ElementKind { + CLASS, + CLASSINSTANCE, + ENUM, + ENUMVALUE, + FUNCTION, + FUNCTIONINSTANCE, + GLOBAL, + INTERFACE, + INTERFACEINSTANCE, + LOCAL, + NAMESPACE +} + +export abstract class Element { + + kind: ElementKind; + program: Program; + internalName: string; + globalExportName: string | null = null; + + constructor(program: Program, internalName: string) { + this.program = program; + this.internalName = internalName; + } +} + +export class Enum extends Element { + + kind = ElementKind.ENUM; + declaration: EnumDeclaration | null; + members: Map = new Map(); + compiled: bool = false; + + constructor(program: Program, internalName: string, declaration: EnumDeclaration | null = null) { + super(program, internalName); + this.declaration = declaration; + } + + get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't exports */ false; } + get isGlobalExport(): bool { return this.globalExportName != null; } + get isConstant(): bool { return this.declaration ? hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are const */ true; } +} + +export class EnumValue extends Element { + + kind = ElementKind.ENUMVALUE; + declaration: EnumValueDeclaration | null; + enum: Enum; + hasConstantValue: bool = false; + constantValue: i32 = 0; + + constructor(enm: Enum, program: Program, internalName: string, declaration: EnumValueDeclaration | null = null) { + super(program, internalName); + this.enum = enm; + if (!(this.declaration = declaration)) this.hasConstantValue = true; + } +} + +export class Global extends Element { + + kind = ElementKind.GLOBAL; + declaration: VariableLikeDeclarationStatement | null; + type: Type | null; + hasConstantValue: bool = false; + constantIntegerValue: I64 = new I64(0, 0); + constantFloatValue: f64 = 0; + compiled: bool = false; + + constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null, type: Type | null) { + super(program, internalName); + if (!(this.declaration = declaration)) this.hasConstantValue = true; + this.type = type; // resolved later if `null` + } + + get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't exports */ false; } + get isGlobalExport(): bool { return this.globalExportName != null; } + get isMutable(): bool { return this.declaration ? hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are immutable */ false; } +} + +export class Namespace extends Element { + + kind = ElementKind.NAMESPACE; + declaration: NamespaceDeclaration | null; + members: Map = new Map(); + + constructor(program: Program, internalName: string, declaration: NamespaceDeclaration | null) { + super(program, internalName); + this.declaration = declaration; + } + + get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : false; } + get isGlobalExport(): bool { return this.declaration ? this.globalExportName != null : /* internals aren't exports */ false; } +} + +export class Parameter { + + name: string; + type: Type; + + constructor(name: string, type: Type) { + this.name = name; + this.type = type; + } +} + +export class Local extends Element { + + kind = ElementKind.LOCAL; + index: i32; + type: Type; + + constructor(program: Program, internalName: string, index: i32, type: Type) { + super(program, internalName); + this.index = index; + this.type = type; + } +} + +export class FunctionTemplate extends Element { + + kind = ElementKind.FUNCTION; + declaration: FunctionDeclaration | null; + instances: Map; + isGeneric: bool; + + constructor(program: Program, internalName: string, declaration: FunctionDeclaration | null) { + super(program, internalName); + this.declaration = declaration; + this.instances = new Map(); + this.isGeneric = declaration ? declaration.typeParameters.length > 0 : false; // builtins set this + } + + get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; } + get isGlobalExport(): bool { return this.declaration ? this.globalExportName != null : /* internals aren't global exports */ false; } + get isGetter(): bool { return this.declaration ? hasModifier(ModifierKind.GET, this.declaration.modifiers) : /* internals aren't getters */ false; } + get isSetter(): bool { return this.declaration ? hasModifier(ModifierKind.SET, this.declaration.modifiers) : /* internals aren't setters */ false; } + + instantiate(typeArguments: Type[], contextualTypeArguments: Map | null): FunctionInstance | null { + const instanceKey: string = typesToString(typeArguments, "", ""); + let instance: FunctionInstance | null = this.instances.get(instanceKey); + if (instance) + return instance; + const declaration: FunctionDeclaration | null = this.declaration; + if (!declaration) + throw new Error("unexpected instantiation of internal function"); + + // override call specific contextual type arguments + let i: i32, k: i32 = typeArguments.length; + if (k) { + const inheritedTypeArguments: Map | null = contextualTypeArguments; + contextualTypeArguments = new Map(); + if (inheritedTypeArguments) + for (let [name, type] of inheritedTypeArguments) + contextualTypeArguments.set(name, type); + for (i = 0; i < k; ++i) + contextualTypeArguments.set(declaration.typeParameters[i].identifier.name, typeArguments[i]); + } + + // resolve parameters + k = declaration.parameters.length; + const parameters: Parameter[] = new Array(k); + const parameterTypes: Type[] = new Array(k); + for (let i = 0; i < k; ++i) { + const typeNode: TypeNode | null = declaration.parameters[i].type; + if (typeNode) { + const type: Type | null = this.program.resolveType(typeNode, contextualTypeArguments, true); // reports + if (type) { + parameters[i] = new Parameter(declaration.parameters[i].identifier.name, type); + parameterTypes[i] = type; + } else + return null; + } else + return null; // TODO: infer type? (currently reported by parser) + } + + // resolve return type + const typeNode: TypeNode | null = declaration.returnType; + let returnType: Type; + if (typeNode) { + const type: Type | null = this.program.resolveType(typeNode, contextualTypeArguments, true); // reports + if (type) + returnType = type; + else + return null; + } else + return null; // TODO: infer type? (currently reported by parser) + + let internalName: string = this.internalName; + if (instanceKey.length) + internalName += "<" + instanceKey + ">"; + instance = new FunctionInstance(this, internalName, typeArguments, parameters, returnType, null); // TODO: class + this.instances.set(instanceKey, instance); + return instance; + } +} + +export class FunctionInstance extends Element { + + kind = ElementKind.FUNCTIONINSTANCE; + template: FunctionTemplate; + typeArguments: Type[]; + parameters: Parameter[]; + returnType: Type; + instanceMethodOf: ClassInstance | null; + locals: Map = new Map(); + additionalLocals: Type[] = []; + breakContext: string | null = null; + compiled: bool = false; + + contextualTypeArguments: Map = new Map(); + + private breakMajor: i32 = 0; + private breakMinor: i32 = 0; + + constructor(template: FunctionTemplate, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: ClassInstance | null) { + super(template.program, internalName); + this.template = template; + this.typeArguments = typeArguments; + this.parameters = parameters; + this.returnType = returnType; + this.instanceMethodOf = instanceMethodOf; + let localIndex: i32 = 0; + if (instanceMethodOf) { + this.locals.set("this", new Local(template.program, "this", localIndex++, instanceMethodOf.type)); + for (let [name, type] of instanceMethodOf.contextualTypeArguments) + this.contextualTypeArguments.set(name, type); + } + for (let i: i32 = 0, k: i32 = parameters.length; i < k; ++i) { + const parameter: Parameter = parameters[i]; + this.locals.set(parameter.name, new Local(template.program, parameter.name, localIndex++, parameter.type)); + } + } + + get isInstance(): bool { return this.instanceMethodOf != null; } + + addLocal(type: Type, name: string | null = null): Local { + // if it has a name, check previously as this method will throw otherwise + let localIndex = this.parameters.length + this.additionalLocals.length; + if (this.instanceMethodOf) localIndex++; // plus 'this' + const local: Local = new Local(this.template.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type); + if (name) { + if (this.locals.has(name)) + throw new Error("unexpected duplicate local name"); + this.locals.set(name, local); + } + this.additionalLocals.push(type); + return local; + } + + enterBreakContext(): string { + if (!this.breakMinor) + this.breakMajor++; + return this.breakContext = this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10); + } + + leaveBreakContext(): void { + if (--this.breakMinor < 0) + throw new Error("unexpected unbalanced break context"); + if (this.breakMinor == 0 && !--this.breakMajor) + this.breakContext = null; + } +} + +export class ClassTemplate extends Namespace { + + kind = ElementKind.CLASS; + declaration: ClassDeclaration | null; + fieldDeclarations: Map; + methodDeclarations: Map; + instances: Map; + isGeneric: bool; + + constructor(program: Program, internalName: string, declaration: ClassDeclaration | null = null) { + super(program, internalName, null); + this.declaration = declaration; + this.fieldDeclarations = new Map(); + this.methodDeclarations = new Map(); + this.instances = new Map(); + this.isGeneric = declaration ? declaration.typeParameters.length > 0 : false; // builtins can set this + } + + get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; } + get isGlobalExport(): bool { return this.declaration ? this.globalExportName != null : /* internals aren't global exports */ false; } + + instantiate(typeArguments: Type[], contextualTypeArguments: Map | null): ClassInstance { + const key: string = typesToString(typeArguments, "", ""); + let instance: ClassInstance | null = this.instances.get(key); + if (instance) + return instance; + if (!this.declaration) + throw new Error("unexpected instantiation of internal class"); + throw new Error("not implemented"); + } +} + +export class ClassInstance extends Element { + + kind = ElementKind.CLASSINSTANCE; + template: ClassTemplate; + typeArguments: Type[]; + base: ClassInstance | null; + type: Type; + compiled: bool = false; + + contextualTypeArguments: Map = new Map(); + + constructor(template: ClassTemplate, internalName: string, typeArguments: Type[], base: ClassInstance | null) { + super(template.program, internalName); + this.template = template; + this.typeArguments = typeArguments; + this.base = base; + this.type = (template.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this); + + // inherit base class contextual type arguments + if (base) + for (let [name, type] of base.contextualTypeArguments) + this.contextualTypeArguments.set(name, type); + + // apply instance-specific contextual type arguments + const declaration: ClassDeclaration | null = this.template.declaration; + if (declaration) { // irrelevant for builtins + const typeParameters: TypeParameter[] = declaration.typeParameters; + if (typeParameters.length != typeArguments.length) + throw new Error("unexpected type argument count mismatch"); + for (let i: i32 = 0, k: i32 = typeArguments.length; i < k; ++i) + this.contextualTypeArguments.set(typeParameters[i].identifier.name, typeArguments[i]); } } } + +export class InterfaceTemplate extends ClassTemplate { + + kind = ElementKind.INTERFACE; + declaration: InterfaceDeclaration | null; + + constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null) { + super(program, internalName, declaration); + } +} + +export class InterfaceInstance extends ClassInstance { + + kind = ElementKind.INTERFACEINSTANCE; + base: InterfaceInstance | null; + + constructor(template: InterfaceTemplate, internalName: string, typeArguments: Type[], base: InterfaceInstance | null) { + super(template, internalName, typeArguments, base); + } +} diff --git a/src/tokenizer.ts b/src/tokenizer.ts index bec2c0f5..cd2908ae 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -280,6 +280,7 @@ export function operatorTokenToString(token: Token): string { const possibleIdentifiers: Set = new Set([ "from", + "global", "module", "namespace", "type" @@ -302,6 +303,9 @@ export class Range { 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); } + + get atStart(): Range { return new Range(this.source, this.start, this.start); } + get atEnd(): Range { return new Range(this.source, this.end, this.end); } } export class Tokenizer extends DiagnosticEmitter { @@ -1107,7 +1111,7 @@ function isOctalDigit(c: i32): bool { function isIdentifierStart(c: i32): bool { return c >= CharCode.A && c <= CharCode.Z || c >= CharCode.a && c <= CharCode.z - || c == CharCode.$ +// || c == CharCode.DOLLAR // reserved for internal in case we have to change the naming scheme || c == CharCode._ || c > 0x7f && isUnicodeIdentifierStart(c); } @@ -1120,7 +1124,7 @@ 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.DOLLAR // reserved for internal use, see above || c == CharCode._ || c > 0x7f && isUnicodeIdentifierPart(c); } diff --git a/src/tsconfig.json b/src/tsconfig.json index 1f3f7f5c..c7126ccf 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -36,5 +36,14 @@ "util/charcode.ts", "util/i64.ts", "util/path.ts" - ] -} \ No newline at end of file + ], + "assembly": { + "exclude": [ + "glue/js.t.ts", + "glue/js.ts" + ], + "include": [ + "glue/wasm.ts" + ] + } +} diff --git a/src/types.ts b/src/types.ts index 67d7f8de..40095f05 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,4 @@ +import { ClassInstance } from "./program"; import { sb } from "./util"; export const enum TypeKind { @@ -28,7 +29,7 @@ export class Type { kind: TypeKind; size: i32; - classType: ClassType | null; + classType: ClassInstance | null; nullable: bool = false; nullableType: Type | null = null; // cached, of this type @@ -49,7 +50,7 @@ export class Type { get isAnySize(): bool { return this.kind == TypeKind.ISIZE || this.kind == TypeKind.USIZE; } get isAnyFloat(): bool { return this.kind == TypeKind.F32 || this.kind == TypeKind.F64; } - asClass(classType: ClassType): Type { + asClass(classType: ClassInstance): Type { const ret: Type = new Type(this.kind, this.size); ret.classType = classType; return ret; @@ -73,7 +74,7 @@ export class Type { case TypeKind.U16: return "u16"; case TypeKind.U32: return "u32"; case TypeKind.U64: return "u64"; - case TypeKind.USIZE: return this.classType && !kindOnly ? this.classType.toString() : "usize"; + case TypeKind.USIZE: return this.classType != null && !kindOnly ? this.classType.toString() : "usize"; case TypeKind.BOOL: return "bool"; case TypeKind.F32: return "f32"; case TypeKind.F64: return "f64"; @@ -101,101 +102,12 @@ export class Type { static readonly void: Type = new Type(TypeKind.VOID, 0); } -export class FunctionType { - - typeArguments: Type[]; - parameterTypes: Type[]; - returnType: Type; - - additionalLocals: Type[]; - typeArgumentsMap: Map | null = null; - locals: Map = new Map(); - breakContext: string | null = null; - - private breakMajor: i32 = 0; - private breakMinor: i32 = 0; - - constructor(typeArguments: Type[], parameterTypes: Type[], returnType: Type, parameterNames: string[] | null = null) { - this.typeArguments = typeArguments; - this.parameterTypes = parameterTypes; - this.returnType = returnType; - this.additionalLocals = new Array(); - if (parameterNames) { - if (parameterTypes.length != (parameterNames).length) - throw new Error("unexpected parameter count mismatch");; - for (let i: i32 = 0, k: i32 = parameterTypes.length; i < k; ++i) { - const name: string = (parameterNames)[i]; - if (this.locals.has(name)) - throw new Error("duplicate parameter name"); - this.locals.set(name, new LocalType(i, parameterTypes[i])); - } - } - } - - enterBreakContext(): string { - if (!this.breakMinor) - this.breakMajor++; - return this.breakContext = this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10); - } - - leaveBreakContext(): void { - if (--this.breakMinor < 0) - throw new Error("unexpected unbalanced break context"); - if (this.breakMinor == 0 && !--this.breakMajor) - this.breakContext = null; - } - - addLocal(type: Type, name: string | null = null): i32 { - // internal locals don't necessarily need names if referenced by index only - if (name && this.locals.has(name)) - throw new Error("duplicate local name"); - const index: i32 = this.parameterTypes.length + this.additionalLocals.length; - this.additionalLocals.push(type); - if (name) - this.locals.set(name, new LocalType(index, type)); - return index; - } -} - -export class LocalType { - - index: i32; - type: Type; - - constructor(index: i32, type: Type) { - this.index = index; - this.type = type; - } -} - -export class ClassType { - - name: string; - typeArguments: Type[]; - base: ClassType | null; - - type: Type; - typeArgumentsMap: Map | null = null; - - constructor(name: string, usizeType: Type, typeArguments: Type[], base: ClassType | null = null) { - this.name = name; - this.typeArguments = typeArguments; - this.base = base; - this.type = usizeType.asClass(this); - } - - toString(): string { - let str: string = this.typeArguments.length ? this.name + typeArgumentsToString(this.typeArguments) : this.name; - return this.type.nullable ? str + " | null" : str; - } -} - -export function typeArgumentsToString(typeArguments: Type[]): string { - const k: i32 = typeArguments.length; +export function typesToString(types: Type[], prefix: string = "<", postfix: string = ">"): string { + const k: i32 = types.length; if (!k) return ""; sb.length = 0; for (let i: i32 = 0; i < k; ++i) - sb[i] = typeArguments[i].toString(); - return "<" + sb.join(",") + ">"; + sb[i] = types[i].toString(); + return prefix + sb.join(",") + postfix; } diff --git a/src/util/charcode.ts b/src/util/charcode.ts index 23dca035..c542b581 100644 --- a/src/util/charcode.ts +++ b/src/util/charcode.ts @@ -27,7 +27,6 @@ export const enum CharCode { OGHAM = 0x1680, _ = 0x5F, - $ = 0x24, _0 = 0x30, _1 = 0x31, @@ -106,6 +105,7 @@ export const enum CharCode { CLOSEPAREN = 0x29, COLON = 0x3A, COMMA = 0x2C, + DOLLAR = 0x24, DOT = 0x2E, DOUBLEQUOTE = 0x22, EQUALS = 0x3D, diff --git a/src/util/i64.ts b/src/util/i64.ts index ab61b573..ccd51db5 100644 --- a/src/util/i64.ts +++ b/src/util/i64.ts @@ -511,6 +511,10 @@ export class U64 extends I64 { return false; } + get fitsInU32(): bool { + return this.hi == 0; + } + 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; diff --git a/std/array.d.ts b/std/array.d.ts new file mode 100644 index 00000000..0bdc66c2 --- /dev/null +++ b/std/array.d.ts @@ -0,0 +1,17 @@ +/// + +declare class Array { + length: i32; + readonly capacity: i32; + readonly data: usize; + constructor(capacity: i32); +} + +declare class Int8Array extends Array {} +declare class Int16Array extends Array {} +declare class Int32Array extends Array {} +declare class Uint8Array extends Array {} +declare class Uint16Array extends Array {} +declare class Uint32Array extends Array {} +declare class Float32Array extends Array {} +declare class Float64Array extends Array {} diff --git a/std/array.ts b/std/array.ts new file mode 100644 index 00000000..327e8ee9 --- /dev/null +++ b/std/array.ts @@ -0,0 +1,17 @@ +/// + +@global() +class Array { + + length: i32; + readonly capacity: i32; + readonly data: usize; + + constructor(capacity: i32) { + if (capacity < 0) + throw new RangeError("capacity out of bounds"); + this.length = capacity; + this.capacity = capacity; + this.data = Memory.allocate(sizeof() * capacity); + } +} diff --git a/std/error.d.ts b/std/error.d.ts new file mode 100644 index 00000000..20055be8 --- /dev/null +++ b/std/error.d.ts @@ -0,0 +1,10 @@ +/// + +declare class Error { + message: string; + constructor(message: string); +} + +declare class RangeError extends Error {} +declare class ReferenceError extends Error {} +declare class TypeError extends Error {} diff --git a/std/error.ts b/std/error.ts new file mode 100644 index 00000000..4c4c9266 --- /dev/null +++ b/std/error.ts @@ -0,0 +1,20 @@ +/// + +@global() +class Error { + + message: string; + + constructor(message: string) { + this.message = message; + } +} + +@global() +class RangeError extends Error {} + +@global() +class ReferenceError extends Error {} + +@global() +class TypeError extends Error {} diff --git a/std/map.d.ts b/std/map.d.ts new file mode 100644 index 00000000..790b8184 --- /dev/null +++ b/std/map.d.ts @@ -0,0 +1 @@ +/// diff --git a/std/map.ts b/std/map.ts new file mode 100644 index 00000000..e96da1b9 --- /dev/null +++ b/std/map.ts @@ -0,0 +1,22 @@ +/// + +@global() +class Map { + private keys: K[]; + private values: V[]; + + constructor() { + this.keys = []; + this.values = []; + } + + has(key: K): bool { + return false; + } + + set(key: K, value: V): void { + } + + clear(): void { + } +} diff --git a/std/math.d.ts b/std/math.d.ts new file mode 100644 index 00000000..78794f4f --- /dev/null +++ b/std/math.d.ts @@ -0,0 +1,4 @@ +/// + +declare class Math { +} diff --git a/std/math.ts b/std/math.ts new file mode 100644 index 00000000..7c352a40 --- /dev/null +++ b/std/math.ts @@ -0,0 +1,5 @@ +/// + +@global() +class Math { +} diff --git a/std/memory.d.ts b/std/memory.d.ts new file mode 100644 index 00000000..9354cec6 --- /dev/null +++ b/std/memory.d.ts @@ -0,0 +1,8 @@ +/// + +declare class Memory { + static allocate(size: usize): usize; + static free(ptr: usize): void; + static copy(src: usize, dst: usize, count: usize): void; + static compare(src: usize, dst: usize, count: usize): i32; +} diff --git a/std/memory.ts b/std/memory.ts new file mode 100644 index 00000000..78edb95f --- /dev/null +++ b/std/memory.ts @@ -0,0 +1,27 @@ +/// + +@global() +class Memory { + + static allocate(size: usize): usize { + const ptr: usize = load(sizeof()); + store(sizeof(), ptr + size); + return ptr; + } + + static free(ptr: usize): void { + } + + static copy(src: usize, dst: usize, count: usize): void { + for (let i: usize = 0; i < count; ++i) + store(dst + i, load(src + i)); + } + + static compare(src: usize, dst: usize, count: usize): i32 { + for (let i: usize = 0; i < count; ++i) { + const d: i32 = (load(src + i) as i32) - (load(dst + i) as i32); + if (d) return d; + } + return 0; + } +} diff --git a/std/set.d.ts b/std/set.d.ts new file mode 100644 index 00000000..428f9efe --- /dev/null +++ b/std/set.d.ts @@ -0,0 +1,4 @@ +/// + +declare class Set { +} diff --git a/std/set.ts b/std/set.ts new file mode 100644 index 00000000..1e2f581b --- /dev/null +++ b/std/set.ts @@ -0,0 +1,5 @@ +/// + +@global() +class Set { +} diff --git a/std/string.d.ts b/std/string.d.ts new file mode 100644 index 00000000..d3b07f84 --- /dev/null +++ b/std/string.d.ts @@ -0,0 +1,11 @@ +/// + +declare class String { + readonly length: i32; + constructor(length: i32); + static fromCharCode(c1: i32, c2?: i32); + static equals(a: string, b: string): bool; + static concat(a: string, b: string): string; + charCodeAt(index: i32): u16; + concat(other: string): string; +} diff --git a/std/string.ts b/std/string.ts new file mode 100644 index 00000000..6cdf71cd --- /dev/null +++ b/std/string.ts @@ -0,0 +1,58 @@ +/// + +@global() +@allocates() +@operator("==", String.equals) +@operator("!=", String.notEquals) +@operator("+", String.concat) +class String { + + readonly length: i32; + + constructor(length: i32) { + if (length < 0) + throw new RangeError("invalid length"); + const data: usize = Memory.allocate(4 + length); + store(data, length); + return classof(data); + } + + static fromCharCode(c1: i32 /* sic */, c2: i32 = -1): String { + throw new Error("not implemented"); + } + + static equals(a: String, b: String): bool { + const aLength: i32 = a.length; + return aLength == b.length && !Memory.compare(pointerof(a) + 4, pointerof(b) + 4, aLength << 1); + } + + static notEquals(a: String, b: String): bool { + const aLength: i32 = a.length; + return aLength != b.length || Memory.compare(pointerof(a) + 4, pointerof(b) + 4, aLength << 1); + } + + static concat(a: String, b: String): String { + const aLength: i32 = a.length; + const bLength: i32 = b.length; + const combinedLength: i32 = aLength + bLength; + if (combinedLength < 0) + throw new RangeError("invalid length"); + const aByteLength: i32 = aLength << 1; + const bByteLength: i32 = bLength << 1; + const data: usize = Memory.allocate(4 + combinedLength); + store(data, combinedLength); + Memory.copy(pointerof(a) + 4, data + 4, aByteLength); + Memory.copy(pointerof(b) + 4, data + 4 + aByteLength, bByteLength); + return classof(data); + } + + charCodeAt(index: i32): u16 { + if (index < 0 || index > this.length) + throw new RangeError("index out of bounds"); + return load(pointerof(this) + 4 + index << 1); + } + + concat(other: String): String { + return String.concat(this, other); + } +} diff --git a/std/tsconfig.json b/std/tsconfig.json new file mode 100644 index 00000000..0809326f --- /dev/null +++ b/std/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "noLib": true, + "experimentalDecorators": true + }, + "files": [ + "array.ts", + "error.ts", + "map.ts", + "math.ts", + "memory.ts", + "set.ts", + "string.ts" + ] +} \ No newline at end of file