diff --git a/README.md b/README.md index 0a4cd0cd..d256e0e1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ AssemblyScript NEXT By compiling syntactially but not necessarily semantically valid TypeScript to [Binaryen](https://github.com/WebAssembly/binaryen) IR, the resulting module can be validated, optimized, emitted in WebAssembly text or binary format and converted to [asm.js](http://asmjs.org) as a polyfill. -The compiler itself utilizies "portable definitions" so it can be compiled to both JavaScript using `tsc` and, eventually, to WebAssembly using `asc`. +The compiler itself utilizes "portable definitions" so it can be compiled to both JavaScript using `tsc` and, eventually, to WebAssembly using `asc`. Development status ------------------ @@ -32,7 +32,7 @@ $> cd next $> npm install ``` -Author your module either using +Author your module using either * the [assembly definitions](./std/assembly.d.ts) ([base config](./std/assembly.json)) if all you care about is targeting WebAssembly/asm.js or * the [portable definitions](./std/portable.d.ts) ([base config](./std/portable.json)) if you also want to compile to JavaScript using `tsc` diff --git a/src/builtins.ts b/src/builtins.ts index 99f7c6dd..9d39d869 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -3,7 +3,7 @@ import { DiagnosticCode } from "./diagnostics"; import { Node, Expression } from "./ast"; import { Type } from "./types"; import { Module, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef } from "./module"; -import { Program, FunctionPrototype, Local } from "./program"; +import { Program, ElementFlags, FunctionPrototype, Local } from "./program"; /** Initializes the specified program with built-in functions. */ export function initialize(program: Program): void { @@ -41,7 +41,7 @@ export function initialize(program: Program): void { function addFunction(program: Program, name: string, isGeneric: bool = false): void { let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); prototype.isBuiltIn = true; - prototype.isGeneric = isGeneric; + if (isGeneric) prototype.isGeneric = true; program.elements.set(name, prototype); } diff --git a/src/compiler.ts b/src/compiler.ts index d36d3ac1..183f1a10 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -20,6 +20,7 @@ import { ClassPrototype, Class, Element, ElementKind, + ElementFlags, Enum, FunctionPrototype, Function, @@ -201,7 +202,7 @@ export class Compiler extends DiagnosticEmitter { if (!typeRef) typeRef = this.module.addFunctionType("v", NativeType.None, []); this.module.setStart( - this.module.addFunction(this.startFunction.template.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) + this.module.addFunction(this.startFunction.prototype.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) ); } @@ -314,11 +315,11 @@ export class Compiler extends DiagnosticEmitter { return element; } - compileGlobal(element: Global): bool { - if (element.isCompiled) + compileGlobal(global: Global): bool { + if (global.isCompiled) return true; - const declaration: VariableLikeDeclarationStatement | null = element.declaration; - let type: Type | null = element.type; + const declaration: VariableLikeDeclarationStatement | null = global.declaration; + let type: Type | null = global.type; if (!type) { if (!declaration) throw new Error("unexpected missing declaration"); @@ -327,33 +328,33 @@ export class Compiler extends DiagnosticEmitter { type = this.program.resolveType(declaration.type); // reports if (!type) return false; - element.type = type; + global.type = type; } if (this.module.noEmit) return true; const nativeType: NativeType = typeToNativeType(type); let initializer: ExpressionRef; let initializeInStart: bool = false; - if (element.hasConstantValue) { + if (global.hasConstantValue) { if (type.isLongInteger) - initializer = element.constantIntegerValue ? this.module.createI64(element.constantIntegerValue.lo, element.constantIntegerValue.hi) : this.module.createI64(0, 0); + initializer = global.constantIntegerValue ? this.module.createI64(global.constantIntegerValue.lo, global.constantIntegerValue.hi) : this.module.createI64(0, 0); else if (type.kind == TypeKind.F32) - initializer = this.module.createF32(element.constantFloatValue); + initializer = this.module.createF32(global.constantFloatValue); else if (type.kind == TypeKind.F64) - initializer = this.module.createF64(element.constantFloatValue); + initializer = this.module.createF64(global.constantFloatValue); else if (type.isSmallInteger) { if (type.isSignedInteger) { const shift: i32 = type.smallIntegerShift; - initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() << shift >> shift : 0); + initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() << shift >> shift : 0); } else - initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & type.smallIntegerMask: 0); + initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() & type.smallIntegerMask: 0); } else - initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() : 0); + initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() : 0); } else if (declaration) { if (declaration.initializer) { initializer = this.compileExpression(declaration.initializer, type); if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) { - if (!element.isMutable) { + if (!global.isMutable) { initializer = this.precomputeExpressionRef(initializer); if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) { this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range); @@ -366,34 +367,35 @@ export class Compiler extends DiagnosticEmitter { initializer = typeToNativeZero(this.module, type); } else throw new Error("unexpected missing declaration or constant value"); - const internalName: string = element.internalName; + const internalName: string = global.internalName; if (initializeInStart) { this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, type)); this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer)); } else { - this.module.addGlobal(internalName, nativeType, element.isMutable, initializer); - if (!element.isMutable) { - element.hasConstantValue = true; + this.module.addGlobal(internalName, nativeType, global.isMutable, initializer); + if (!global.isMutable) { const exprType: NativeType = _BinaryenExpressionGetType(initializer); switch (exprType) { case NativeType.I32: - element.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initializer), 0); + global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initializer), 0); break; case NativeType.I64: - element.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initializer), _BinaryenConstGetValueI64High(initializer)); + global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initializer), _BinaryenConstGetValueI64High(initializer)); break; case NativeType.F32: - element.constantFloatValue = _BinaryenConstGetValueF32(initializer); + global.constantFloatValue = _BinaryenConstGetValueF32(initializer); break; case NativeType.F64: - element.constantFloatValue = _BinaryenConstGetValueF64(initializer); + global.constantFloatValue = _BinaryenConstGetValueF64(initializer); break; default: throw new Error("unexpected initializer type"); } + global.hasConstantValue = true; } } - return element.isCompiled = true; + global.isCompiled = true; + return true; } // enums @@ -446,8 +448,8 @@ export class Compiler extends DiagnosticEmitter { } else { this.module.addGlobal(val.internalName, NativeType.I32, false, initializer); if (_BinaryenExpressionGetType(initializer) == NativeType.I32) { - val.hasConstantValue = true; val.constantValue = _BinaryenConstGetValueI32(initializer); + val.hasConstantValue = true; } else throw new Error("unexpected initializer type"); } @@ -483,11 +485,11 @@ export class Compiler extends DiagnosticEmitter { if (instance.isCompiled) return true; - const declaration: FunctionDeclaration | null = instance.template.declaration; + const declaration: FunctionDeclaration | null = instance.prototype.declaration; if (!declaration) throw new Error("unexpected missing declaration"); - if (instance.isDeclare) { + if (instance.isDeclared) { if (declaration.statements) { this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, declaration.identifier.range); return false; @@ -502,7 +504,7 @@ export class Compiler extends DiagnosticEmitter { // compile statements let stmts: ExpressionRef[] | null = null; - if (!instance.isDeclare) { + if (!instance.isDeclared) { const previousFunction: Function = this.currentFunction; this.currentFunction = instance; stmts = this.compileStatements(declaration.statements); @@ -525,7 +527,7 @@ export class Compiler extends DiagnosticEmitter { // create the function const internalName: string = instance.internalName; - if (instance.isDeclare) { + if (instance.isDeclared) { this.module.addFunctionImport(internalName, "env", declaration.identifier.name, typeRef); } else { this.module.addFunction(internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, stmts, NativeType.None)); @@ -580,7 +582,7 @@ export class Compiler extends DiagnosticEmitter { switch (element.kind) { case ElementKind.CLASS_PROTOTYPE: - if ((noTreeShaking || (element).isExport) && !(element).isGeneric) + if ((noTreeShaking || (element).isExported) && !(element).isGeneric) this.compileClassUsingTypeArguments(element, []); break; @@ -589,7 +591,7 @@ export class Compiler extends DiagnosticEmitter { break; case ElementKind.FUNCTION_PROTOTYPE: - if ((noTreeShaking || (element).isExport) && !(element).isGeneric) + if ((noTreeShaking || (element).isExported) && !(element).isGeneric) this.compileFunctionUsingTypeArguments(element, []); break; @@ -1680,7 +1682,7 @@ export class Compiler extends DiagnosticEmitter { this.compileFunction(functionInstance); // imported function - if (functionInstance.isDeclare) + if (functionInstance.isDeclared) return this.module.createCallImport(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType)); // internal function diff --git a/src/program.ts b/src/program.ts index 4efa118c..2915c46a 100644 --- a/src/program.ts +++ b/src/program.ts @@ -61,6 +61,7 @@ class QueuedImport { const noTypesYet: Map = new Map(); +/** Represents an AssemblyScript program. */ export class Program extends DiagnosticEmitter { /** Array of source files. */ @@ -76,6 +77,7 @@ export class Program extends DiagnosticEmitter { /** Exports of individual files by internal name. Not global exports. */ exports: Map = new Map(); + /** Constructs a new program, optionally inheriting parser diagnostics. */ constructor(diagnostics: DiagnosticMessage[] | null = null) { super(diagnostics); this.sources = new Array(); @@ -152,7 +154,7 @@ export class Program extends DiagnosticEmitter { let element: Element | null; - // queued imports should be resolvable now + // queued imports should be resolvable now through traversing exports and queued exports for (let i: i32 = 0; i < queuedImports.length;) { const queuedImport: QueuedImport = queuedImports[i]; element = this.tryResolveImport(queuedImport.referencedName, queuedExports); @@ -165,7 +167,7 @@ export class Program extends DiagnosticEmitter { } } - // queued exports should be resolvable noww + // queued exports should be resolvable now that imports are finalized for (let [exportName, queuedExport] of queuedExports) { let currentExport: QueuedExport | null = queuedExport; do { @@ -190,6 +192,7 @@ export class Program extends DiagnosticEmitter { } } + /** Tries to resolve an import by traversing exports and queued exports. */ private tryResolveImport(referencedName: string, queuedExports: Map): Element | null { let element: Element | null; do { @@ -215,7 +218,7 @@ export class Program extends DiagnosticEmitter { } const prototype: ClassPrototype = new ClassPrototype(this, internalName, declaration); this.elements.set(internalName, prototype); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (prototype.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else @@ -281,7 +284,7 @@ export class Program extends DiagnosticEmitter { } const enm: Enum = new Enum(this, internalName, declaration); this.elements.set(internalName, enm); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (enm.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else @@ -311,12 +314,10 @@ export class Program extends DiagnosticEmitter { private initializeExport(member: ExportMember, internalPath: string | null, queuedExports: Map): void { const externalName: string = member.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name; - if (this.exports.has(externalName)) { this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName); return; } - let referencedName: string; // export local element @@ -393,15 +394,12 @@ export class Program extends DiagnosticEmitter { return; } this.elements.set(internalName, prototype); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (prototype.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else this.exports.set(internalName, prototype); } - if (hasModifier(ModifierKind.DECLARE, declaration.modifiers)) { - prototype.isDeclare = true; - } } private initializeImports(statement: ImportStatement, queuedExports: Map, queuedImports: QueuedImport[]): void { @@ -459,16 +457,17 @@ export class Program extends DiagnosticEmitter { private initializeInterface(declaration: InterfaceDeclaration): void { const internalName: string = declaration.internalName; - const interfacePrototype: InterfacePrototype = new InterfacePrototype(this, internalName, declaration); + const prototype: InterfacePrototype = new InterfacePrototype(this, internalName, declaration); if (this.elements.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else - this.elements.set(internalName, interfacePrototype); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + this.elements.set(internalName, prototype); + + if (prototype.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else - this.exports.set(internalName, interfacePrototype); + this.exports.set(internalName, prototype); } const memberDeclarations: DeclarationStatement[] = declaration.members; for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) { @@ -476,11 +475,11 @@ export class Program extends DiagnosticEmitter { switch (memberDeclaration.kind) { case NodeKind.FIELD: - this.initializeField(memberDeclaration, interfacePrototype); + this.initializeField(memberDeclaration, prototype); break; case NodeKind.METHOD: - this.initializeMethod(memberDeclaration, interfacePrototype); + this.initializeMethod(memberDeclaration, prototype); break; default: @@ -492,11 +491,12 @@ export class Program extends DiagnosticEmitter { private initializeNamespace(declaration: NamespaceDeclaration): void { const internalName: string = declaration.internalName; const namespace: 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, namespace); - if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (namespace.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else @@ -504,8 +504,8 @@ export class Program extends DiagnosticEmitter { } } const members: Statement[] = declaration.members; - for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) { - const statement: Statement = members[j]; + for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { + const statement: Statement = members[i]; switch (statement.kind) { case NodeKind.CLASS: @@ -540,7 +540,6 @@ export class Program extends DiagnosticEmitter { private initializeVariables(statement: VariableStatement, isNamespaceMember: bool = false): void { const declarations: VariableDeclaration[] = statement.declarations; - const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers); for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; const internalName: string = declaration.internalName; @@ -549,7 +548,7 @@ export class Program extends DiagnosticEmitter { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else { this.elements.set(internalName, global); - if (isExport) { + if (global.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else @@ -559,6 +558,7 @@ export class Program extends DiagnosticEmitter { } } + /** Resolves a {@link TypeNode} to a concrete {@link Type}. */ resolveType(node: TypeNode, contextualTypeArguments: Map | null = null, reportNotFound: bool = true): Type | null { // resolve parameters @@ -596,6 +596,7 @@ export class Program extends DiagnosticEmitter { return null; } + /** Resolves {@link TypeParameter}s to concrete {@link Type}s. */ resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Type[] | null { const parameterCount: i32 = typeParameters.length; const argumentCount: i32 = typeArgumentNodes ? typeArgumentNodes.length : 0; @@ -684,83 +685,193 @@ function checkGlobalDecorator(decorators: Decorator[]): string | null { return null; } +/** Indicates the specific kind of an {@link Element}. */ export enum ElementKind { + /** A {@link ClassPrototype}. */ CLASS_PROTOTYPE, + /** A {@link Class}. */ CLASS, + /** An {@link Enum}. */ ENUM, + /** An {@link EnumValue}. */ ENUMVALUE, + /** A {@link FieldPrototype}. */ FIELD_PROTOTYPE, + /** A {@link Field}. */ FIELD, + /** A {@link FunctionPrototype}. */ FUNCTION_PROTOTYPE, + /** A {@link Function}. */ FUNCTION, + /** A {@link Global}. */ GLOBAL, + /** An {@link InterfacePrototype}. */ INTERFACE_PROTOTYPE, + /** An {@link Interface}. */ INTERFACE, + /** A {@link Local}. */ LOCAL, + /** A {@link Namespace}. */ NAMESPACE } +/** Indicates traits of an {@link Element}. */ +export enum ElementFlags { + /** No flags set. */ + NONE = 0, + /** Is compiled. */ + COMPILED = 1 << 0, + /** Is an import. */ + IMPORTED = 1 << 1, + /** Is an export. */ + EXPORTED = 1 << 2, + /** Is built-in. */ + BUILTIN = 1 << 3, + /** Is declared. */ + DECLARED = 1 << 4, + /** Is generic. */ + GENERIC = 1 << 5, + /** Is constant. */ + CONSTANT = 1 << 6, + /** Has constant value. */ + CONSTANT_VALUE = 1 << 7, + /** Is instance member. */ + INSTANCE = 1 << 8, + /** Is getter. */ + GETTER = 1 << 9, + /** Is setter. */ + SETTER = 1 << 10 +} + /** Base class of all program elements. */ export abstract class Element { + /** Specific element kind. */ kind: ElementKind; + /** Containing {@link Program}. */ program: Program; + /** Internal name referring to this element. */ internalName: string; - isCompiled: bool = false; - isImport: bool = false; - isBuiltIn: bool = false; - isDeclare: bool = false; + /** Element flags. */ + flags: ElementFlags = ElementFlags.NONE; - constructor(program: Program, internalName: string) { + /** Constructs a new element, linking it to its containing {@link Program}. */ + protected constructor(program: Program, internalName: string) { this.program = program; this.internalName = internalName; } + + /** Whether compiled or not. */ + get isCompiled(): bool { return (this.flags & ElementFlags.COMPILED) != 0; } + set isCompiled(is: bool) { if (is) this.flags |= ElementFlags.COMPILED; else this.flags &= ~ElementFlags.COMPILED; } + + /** Whether imported or not. */ + get isImported(): bool { return (this.flags & ElementFlags.IMPORTED) != 0; } + set isImported(is: bool) { if (is) this.flags |= ElementFlags.IMPORTED; else this.flags &= ~ElementFlags.IMPORTED; } + + /** Whether exported or not. */ + get isExported(): bool { return (this.flags & ElementFlags.EXPORTED) != 0; } + set isExported(is: bool) { if (is) this.flags |= ElementFlags.EXPORTED; else this.flags &= ~ElementFlags.EXPORTED; } + + /** Whether built-in or not. */ + get isBuiltIn(): bool { return (this.flags & ElementFlags.BUILTIN) != 0; } + set isBuiltIn(is: bool) { if (is) this.flags |= ElementFlags.BUILTIN; else this.flags &= ~ElementFlags.BUILTIN; } + + /** Whether declared or not. */ + get isDeclared(): bool { return (this.flags & ElementFlags.DECLARED) != 0; } + set isDeclared(is: bool) { if (is) this.flags |= ElementFlags.DECLARED; else this.flags &= ~ElementFlags.DECLARED; } + + /** Whether generic or not. */ + get isGeneric(): bool { return (this.flags & ElementFlags.GENERIC) != 0; } + set isGeneric(is: bool) { if (is) this.flags |= ElementFlags.GENERIC; else this.flags &= ~ElementFlags.GENERIC; } + + /** Whether constant or not. */ + get isConstant(): bool { return (this.flags & ElementFlags.CONSTANT) != 0; } + set isConstant(is: bool) { if (is) this.flags |= ElementFlags.CONSTANT; else this.flags &= ~ElementFlags.CONSTANT; } + + /** Whether mutable or not. */ + get isMutable(): bool { return !(this.flags & ElementFlags.CONSTANT); } // reuses constant flag + set isMutable(is: bool) { if (is) this.flags &= ~ElementFlags.CONSTANT; else this.flags |= ElementFlags.CONSTANT; } + + /** Whether this element has a constant value or not. */ + get hasConstantValue(): bool { return (this.flags & ElementFlags.CONSTANT_VALUE) != 0; } + set hasConstantValue(is: bool) { if (is) this.flags |= ElementFlags.CONSTANT_VALUE; else this.flags &= ~ElementFlags.CONSTANT_VALUE; } + + /** Whether an instance member or not. */ + get isInstance(): bool { return (this.flags & ElementFlags.INSTANCE) != 0; } + set isInstance(is: bool) { if (is) this.flags |= ElementFlags.INSTANCE; else this.flags &= ~ElementFlags.INSTANCE; } } /** A namespace. Also the base class of other namespace-like program elements. */ export class Namespace extends Element { kind = ElementKind.NAMESPACE; + + /** Declaration reference. */ declaration: NamespaceDeclaration | null; + /** Member elements. */ members: Map = new Map(); - constructor(program: Program, internalName: string, declaration: NamespaceDeclaration | null) { + /** Constructs a new namespace. */ + constructor(program: Program, internalName: string, declaration: NamespaceDeclaration | null = null) { super(program, internalName); - this.declaration = declaration; + if ((this.declaration = declaration) && this.declaration.modifiers) { + for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) { + switch (this.declaration.modifiers[i].modifierKind) { + case ModifierKind.IMPORT: this.isImported = true; break; + case ModifierKind.EXPORT: this.isExported = true; break; + case ModifierKind.DECLARE: this.isDeclared = true; break; + default: throw new Error("unexpected modifier"); + } + } + } } - - get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : false; } } /** An enum. */ export class Enum extends Namespace { kind = ElementKind.ENUM; + + /** Declaration reference. */ declaration: EnumDeclaration | null; + /** Enum members. */ members: Map = new Map(); // more specific + /** Constructs a new enum. */ constructor(program: Program, internalName: string, declaration: EnumDeclaration | null = null) { super(program, internalName, null); - this.declaration = declaration; + if ((this.declaration = declaration) && this.declaration.modifiers) { + for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) { + switch (this.declaration.modifiers[i].modifierKind) { + case ModifierKind.EXPORT: this.isExported = true; break; + case ModifierKind.IMPORT: this.isImported = true; break; + case ModifierKind.DECLARE: this.isDeclared = true; break; + case ModifierKind.CONST: this.isConstant = true; break; + default: throw new Error("unexpected modifier"); + } + } + } } - - get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't exports */ false; } - get isConstant(): bool { return this.declaration ? hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are const */ true; } } /** An enum value. */ export class EnumValue extends Element { kind = ElementKind.ENUMVALUE; + + /** Declaration reference. */ declaration: EnumValueDeclaration | null; + /** Parent enum. */ enum: Enum; - hasConstantValue: bool; + /** Constant value, if applicable. */ 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; + if (!(this.declaration = declaration)) + this.hasConstantValue = true; // built-ins have constant values } } @@ -768,29 +879,50 @@ export class EnumValue extends Element { export class Global extends Element { kind = ElementKind.GLOBAL; + + /** Declaration reference. */ declaration: VariableLikeDeclarationStatement | null; + /** Resolved type, if resolved. */ type: Type | null; - hasConstantValue: bool = false; + /** Constant integer value, if applicable. */ constantIntegerValue: I64 | null = null; + /** Constant float value, if applicable. */ constantFloatValue: f64 = 0; - constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null, type: Type | null) { + constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null = null, type: Type | null = null) { super(program, internalName); - if (!(this.declaration = declaration)) this.hasConstantValue = true; - this.type = type; // resolved later if `null`, also updates constantKind + if (this.declaration = declaration) { + if (this.declaration.modifiers) { + for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) { + switch (this.declaration.modifiers[i].modifierKind) { + case ModifierKind.IMPORT: this.isImported = true; break; + case ModifierKind.EXPORT: this.isExported = true; break; + case ModifierKind.CONST: this.isConstant = true; break; + case ModifierKind.DECLARE: this.isDeclared = true; break; + default: throw new Error("unexpected modifier"); + } + } + } + } else { + this.hasConstantValue = true; // built-ins have constant values + } + 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 isMutable(): bool { return this.declaration ? !hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are immutable */ false; } } /** A function parameter. */ export class Parameter { + // not an Element on its own + + /** Parameter name. */ name: string; + /** Parameter type. */ type: Type; + /** Parameter initializer. */ initializer: Expression | null; + /** Constructs a new function parameter. */ constructor(name: string, type: Type, initializer: Expression | null = null) { this.name = name; this.type = type; @@ -802,7 +934,10 @@ export class Parameter { export class Local extends Element { kind = ElementKind.LOCAL; + + /** Local index. */ index: i32; + /** Local type. */ type: Type; constructor(program: Program, internalName: string, index: i32, type: Type) { @@ -816,22 +951,44 @@ export class Local extends Element { export class FunctionPrototype extends Element { kind = ElementKind.FUNCTION_PROTOTYPE; - declaration: FunctionDeclaration | null; - classPrototype: ClassPrototype | null; - instances: Map = new Map(); - isGeneric: bool; + /** Declaration reference. */ + declaration: FunctionDeclaration | null; + /** Class prototype reference. */ + classPrototype: ClassPrototype | null; + /** Resolved instances. */ + instances: Map = new Map(); + + /** Constructs a new function prototype. */ constructor(program: Program, internalName: string, declaration: FunctionDeclaration | null, classPrototype: ClassPrototype | null = null) { super(program, internalName); - this.declaration = declaration; - this.classPrototype = classPrototype; - this.isGeneric = declaration ? declaration.typeParameters.length > 0 : false; // built-ins set this + if (this.declaration = declaration) { + if (this.declaration.modifiers) + for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) { + switch (this.declaration.modifiers[i].modifierKind) { + case ModifierKind.IMPORT: this.isImported = true; break; + case ModifierKind.EXPORT: this.isExported = true; break; + case ModifierKind.DECLARE: this.isDeclared = true; break; + case ModifierKind.GET: this.isGetter = true; break; + case ModifierKind.SET: this.isSetter = true; break; + default: throw new Error("unexpected modifier"); + } + } + if (this.declaration.typeParameters.length) + this.isGeneric = true; + } + if (this.classPrototype = classPrototype) { + this.isInstance = true; + } } - get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; } - get isInstance(): bool { return this.classPrototype != null; } - 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; } + /** Whether a getter function or not. */ + get isGetter(): bool { return (this.flags & ElementFlags.GETTER) != 0; } + set isGetter(is: bool) { if (is) this.flags |= ElementFlags.GETTER; else this.flags &= ~ElementFlags.GETTER; } + + /** Whether a setter function or not. */ + get isSetter(): bool { return (this.flags & ElementFlags.SETTER) != 0; } + set isSetter(is: bool) { if (is) this.flags |= ElementFlags.SETTER; else this.flags &= ~ElementFlags.SETTER; } resolve(typeArguments: Type[], contextualTypeArguments: Map | null): Function | null { const instanceKey: string = typesToString(typeArguments, "", ""); @@ -912,8 +1069,8 @@ export class Function extends Element { kind = ElementKind.FUNCTION; - /** Underlying function template. */ - template: FunctionPrototype; + /** Prototype reference. */ + prototype: FunctionPrototype; /** Concrete type arguments. */ typeArguments: Type[]; /** Concrete function parameters. Excluding `this` if an instance method. */ @@ -937,13 +1094,12 @@ export class Function extends Element { /** Constructs a new concrete function. */ constructor(prototype: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) { super(prototype.program, internalName); - this.template = prototype; + this.prototype = prototype; this.typeArguments = typeArguments; this.parameters = parameters; this.returnType = returnType; this.instanceMethodOf = instanceMethodOf; - this.isBuiltIn = prototype.isBuiltIn; - this.isDeclare = prototype.isDeclare; + this.flags = prototype.flags; let localIndex: i32 = 0; if (instanceMethodOf) { this.locals.set("this", new Local(prototype.program, "this", localIndex++, instanceMethodOf.type)); @@ -964,7 +1120,7 @@ export class Function extends Element { // if it has a name, check previously as this method will throw otherwise let localIndex = this.parameters.length + this.additionalLocals.length; if (this.isInstance) localIndex++; // plus 'this' - const local: Local = new Local(this.template.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type); + const local: Local = new Local(this.prototype.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type); if (name) { if (this.locals.has(name)) throw new Error("unexpected duplicate local name"); @@ -1050,30 +1206,45 @@ export class Function extends Element { export class FieldPrototype extends Element { kind = ElementKind.FIELD_PROTOTYPE; + + /** Declaration reference. */ declaration: FieldDeclaration | null; + /** Parent class prototype. */ classPrototype: ClassPrototype; - constructor(classPrototype: ClassPrototype, internalName: string, declaration: FieldDeclaration | null) { + /** Constructs a new field prototype. */ + constructor(classPrototype: ClassPrototype, internalName: string, declaration: FieldDeclaration | null = null) { super(classPrototype.program, internalName); this.classPrototype = classPrototype; + if ((this.declaration = declaration) && this.declaration.modifiers) { + for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) { + switch (this.declaration.modifiers[i].modifierKind) { + case ModifierKind.EXPORT: this.isExported = true; break; + default: throw new Error("unexpected modifier"); + } + } + } } - - get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; } } /** A resolved instance field. */ export class Field extends Element { kind = ElementKind.FIELD; - template: FieldPrototype; + + /** Field prototype reference. */ + prototype: FieldPrototype; + /** Resolved type. */ type: Type; - hasConstantValue: bool = false; + /** Constant integer value, if applicable. */ constantIntegerValue: I64 | null = null; + /** Constant float value, if applicable. */ constantFloatValue: f64 = 0; - constructor(template: FieldPrototype, internalName: string, type: Type) { - super(template.program, internalName); - if (!this.template.declaration) this.hasConstantValue = true; + /** Constructs a new field. */ + constructor(prototype: FieldPrototype, internalName: string, type: Type) { + super(prototype.program, internalName); + this.flags = prototype.flags; this.type = type; } } @@ -1082,19 +1253,31 @@ export class Field extends Element { export class ClassPrototype extends Namespace { kind = ElementKind.CLASS_PROTOTYPE; + + /** Declaration reference. */ declaration: ClassDeclaration | null; + /** Resolved instances. */ instances: Map; - isGeneric: bool; constructor(program: Program, internalName: string, declaration: ClassDeclaration | null = null) { super(program, internalName, null); - this.declaration = declaration; + if (this.declaration = declaration) { + if (this.declaration.modifiers) { + for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) { + switch (this.declaration.modifiers[i].modifierKind) { + case ModifierKind.IMPORT: this.isImported = true; break; + case ModifierKind.EXPORT: this.isExported = true; break; + case ModifierKind.DECLARE: this.isDeclared = true; break; + default: throw new Error("unexpected modifier"); + } + } + } + if (this.declaration.typeParameters.length) + this.isGeneric = true; + } 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; } - resolve(typeArguments: Type[], contextualTypeArguments: Map | null): Class { const key: string = typesToString(typeArguments, "", ""); let instance: Class | null = this.instances.get(key); @@ -1125,20 +1308,26 @@ export class ClassPrototype extends Namespace { export class Class extends Namespace { kind = ElementKind.CLASS; - declaration: ClassDeclaration | null; - template: ClassPrototype; + + /** Prototype reference. */ + prototype: ClassPrototype; + /** Resolved type arguments. */ typeArguments: Type[]; - base: Class | null; + /** Resolved class type. */ type: Type; + /** Base class, if applicable. */ + base: Class | null; contextualTypeArguments: Map = new Map(); - constructor(template: ClassPrototype, internalName: string, typeArguments: Type[], base: Class | null) { - super(template.program, internalName, template.declaration); - this.template = template; + /** Constructs a new class. */ + constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] = [], base: Class | null = null) { + super(prototype.program, internalName, prototype.declaration); + this.prototype = prototype; + this.flags = prototype.flags; this.typeArguments = typeArguments; this.base = base; - this.type = (template.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this); + this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this); // inherit base class contextual type arguments if (base) @@ -1146,7 +1335,7 @@ export class Class extends Namespace { this.contextualTypeArguments.set(name, type); // apply instance-specific contextual type arguments - const declaration: ClassDeclaration | null = this.template.declaration; + const declaration: ClassDeclaration | null = this.prototype.declaration; if (declaration) { // irrelevant for built-ins const typeParameters: TypeParameter[] = declaration.typeParameters; if (typeParameters.length != typeArguments.length) @@ -1165,9 +1354,12 @@ export class Class extends Namespace { export class InterfacePrototype extends ClassPrototype { kind = ElementKind.INTERFACE_PROTOTYPE; + + /** Declaration reference. */ declaration: InterfaceDeclaration | null; - constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null) { + /** Constructs a new interface prototype. */ + constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null = null) { super(program, internalName, declaration); } } @@ -1176,10 +1368,14 @@ export class InterfacePrototype extends ClassPrototype { export class Interface extends Class { kind = ElementKind.INTERFACE; - template: InterfacePrototype; + + /** Prototype reference. */ + prototype: InterfacePrototype; + /** Base interface, if applcable. */ base: Interface | null; - constructor(template: InterfacePrototype, internalName: string, typeArguments: Type[], base: Interface | null) { - super(template, internalName, typeArguments, base); + /** Constructs a new interface. */ + constructor(prototype: InterfacePrototype, internalName: string, typeArguments: Type[] = [], base: Interface | null = null) { + super(prototype, internalName, typeArguments, base); } }