import { initialize as initializeBuiltins } from "./builtins"; import { Target, typeToNativeType } from "./compiler"; import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER } from "./constants"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { Type, typesToString } from "./types"; import { I64 } from "./util/i64"; import { ModifierKind, Node, NodeKind, Source, Range, TypeNode, Expression, IdentifierExpression, LiteralExpression, LiteralKind, PropertyAccessExpression, StringLiteralExpression, ClassDeclaration, DeclarationStatement, Decorator, EnumDeclaration, EnumValueDeclaration, ExportMember, ExportStatement, FieldDeclaration, FunctionDeclaration, ImportDeclaration, ImportStatement, InterfaceDeclaration, MethodDeclaration, Modifier, NamespaceDeclaration, Statement, TypeParameter, VariableLikeDeclarationStatement, VariableDeclaration, VariableStatement, hasModifier, mangleInternalName } from "./ast"; import { NativeType } from "./module"; class QueuedExport { isReExport: bool; referencedName: string; member: ExportMember; } class QueuedImport { internalName: string; referencedName: string; declaration: ImportDeclaration; } const noTypesYet: Map = new Map(); /** Represents an AssemblyScript program. */ export class Program extends DiagnosticEmitter { /** Array of source files. */ sources: Source[]; /** Diagnostic offset used where sequentially obtaining the next diagnostic. */ diagnosticsOffset: i32 = 0; /** WASM target. */ target: Target = Target.WASM32; // set on initialization /** Elements by internal name. */ elements: Map = new Map(); /** Types by internal name. */ types: Map = noTypesYet; /** 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(); } /** Initializes the program and its elements prior to compilation. */ initialize(target: Target = Target.WASM32): void { this.target = target; this.types = new Map([ ["i8", Type.i8], ["i16", Type.i16], ["i32", Type.i32], ["i64", Type.i64], ["isize", target == Target.WASM64 ? Type.isize64 : Type.isize32], ["u8", Type.u8], ["u16", Type.u16], ["u32", Type.u32], ["u64", Type.u64], ["usize", target == Target.WASM64 ? Type.usize64 : Type.usize32], ["bool", Type.bool], ["f32", Type.f32], ["f64", Type.f64], ["void", Type.void] ]); initializeBuiltins(this); const queuedExports: Map = new Map(); const queuedImports: QueuedImport[] = new Array(); // build initial lookup maps of internal names to declarations for (let i: i32 = 0, k: i32 = this.sources.length; i < k; ++i) { const source: Source = this.sources[i]; const statements: Statement[] = source.statements; for (let j: i32 = 0, l: i32 = statements.length; j < l; ++j) { const statement: Statement = statements[j]; switch (statement.kind) { case NodeKind.CLASS: this.initializeClass(statement); break; case NodeKind.ENUM: this.initializeEnum(statement); break; case NodeKind.EXPORT: this.initializeExports(statement, queuedExports); break; case NodeKind.FUNCTION: this.initializeFunction(statement); break; case NodeKind.IMPORT: this.initializeImports(statement, queuedExports, queuedImports); break; case NodeKind.INTERFACE: this.initializeInterface(statement); break; case NodeKind.NAMESPACE: this.initializeNamespace(statement); break; case NodeKind.VARIABLE: this.initializeVariables(statement); break; } } } let element: Element | null; // 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); if (element) { this.elements.set(queuedImport.internalName, element); queuedImports.splice(i, 1); } else { this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedImport.declaration.range, (queuedImport.declaration.parent).path.value, queuedImport.declaration.externalIdentifier.name); ++i; } } // queued exports should be resolvable now that imports are finalized for (let [exportName, queuedExport] of queuedExports) { let currentExport: QueuedExport | null = queuedExport; do { if (currentExport.isReExport) { element = this.exports.get(currentExport.referencedName); if (element) { this.exports.set(exportName, element); break; } currentExport = queuedExports.get(currentExport.referencedName); if (!currentExport) this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.member.externalIdentifier.range, ((queuedExport.member.parent).path).value, queuedExport.member.externalIdentifier.name); } else { element = this.elements.get(currentExport.referencedName); if (element) this.exports.set(exportName, element); else this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.range, queuedExport.member.identifier.name); break; } } while (currentExport); } } /** Tries to resolve an import by traversing exports and queued exports. */ private tryResolveImport(referencedName: string, queuedExports: Map): Element | null { let element: Element | null; do { element = this.exports.get(referencedName); if (element) return element; const queuedExport: QueuedExport | null = queuedExports.get(referencedName); if (!queuedExport) return null; if (queuedExport.isReExport) { referencedName = queuedExport.referencedName; continue; } return this.elements.get(queuedExport.referencedName); } while (true); } private initializeClass(declaration: ClassDeclaration, namespace: Namespace | null = null): void { throw new Error("not implemented"); /* const internalName: string = declaration.internalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); return; } const prototype: ClassPrototype = new ClassPrototype(this, internalName, declaration); 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, prototype); } const memberDeclarations: DeclarationStatement[] = declaration.members; for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) { const memberDeclaration: DeclarationStatement = memberDeclarations[j]; if (memberDeclaration.kind == NodeKind.FIELD) this.initializeField(memberDeclaration, prototype); else if (memberDeclaration.kind == NodeKind.METHOD) this.initializeMethod(memberDeclaration, prototype); else throw new Error("unexpected class member"); } */ } private initializeField(declaration: FieldDeclaration, classPrototype: ClassPrototype): void { throw new Error("not implemented"); /* const name: string = declaration.identifier.name; if (classPrototype.members.has(name)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, declaration.internalName); return; } const internalName: string = declaration.internalName; if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) { // static fields become globals const global: Global = new Global(this, internalName, declaration, null); classPrototype.members.set(name, global); } else { const field: FieldPrototype = new FieldPrototype(classPrototype, internalName, declaration); classPrototype.members.set(name, field); } */ } private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void { throw new Error("not implemented"); /* let name: string = declaration.identifier.name; const internalName: string = declaration.internalName; if (classPrototype.members.has(name)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); return; } const func: FunctionPrototype = new FunctionPrototype(this, internalName, declaration, hasModifier(ModifierKind.STATIC, declaration.modifiers) ? null : classPrototype); 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; } } } classPrototype.members.set(name, func); */ } private initializeEnum(declaration: EnumDeclaration, namespace: Namespace | null = null): void { const internalName: string = declaration.internalName; 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 (namespace) { if (namespace.members.has(declaration.identifier.name)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); } else namespace.members.set(declaration.identifier.name, enm); } else if (enm.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, enm); } const values: EnumValueDeclaration[] = declaration.members; for (let i: i32 = 0, k: i32 = values.length; i < k; ++i) this.initializeEnumValue(values[i], enm); } 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); return; } 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.internalPath, queuedExports); } 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 if (internalPath == null) { referencedName = member.range.source.internalPath + PATH_DELIMITER + member.identifier.name; // resolve right away if the element exists if (this.elements.has(referencedName)) { this.exports.set(externalName, this.elements.get(referencedName)); return; } // otherwise queue it if (queuedExports.has(externalName)) { this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName); return; } const queuedExport: QueuedExport = new QueuedExport(); queuedExport.isReExport = false; queuedExport.referencedName = referencedName; // -> internal name queuedExport.member = member; queuedExports.set(externalName, queuedExport); // export external element } else { referencedName = (internalPath) + PATH_DELIMITER + member.identifier.name; // resolve right away if the export exists if (this.exports.has(referencedName)) { this.exports.set(externalName, this.exports.get(referencedName)); return; } // walk already known queued exports const seen: Set = new Set(); while (queuedExports.has(referencedName)) { const queuedExport: QueuedExport = queuedExports.get(referencedName); if (queuedExport.isReExport) { if (this.exports.has(queuedExport.referencedName)) { this.exports.set(externalName, this.exports.get(referencedName)); return; } referencedName = queuedExport.referencedName; if (seen.has(queuedExport)) break; seen.add(queuedExport); } else { if (this.elements.has(queuedExport.referencedName)) { this.exports.set(externalName, this.elements.get(referencedName)); return; } break; } } // otherwise queue it if (queuedExports.has(externalName)) { this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName); return; } const queuedReExport: QueuedExport = new QueuedExport(); queuedReExport.isReExport = true; queuedReExport.referencedName = referencedName; // -> export name queuedReExport.member = member; queuedExports.set(externalName, queuedReExport); } } private initializeFunction(declaration: FunctionDeclaration, namespace: Namespace | null = null): void { const internalName: string = declaration.internalName; const prototype: FunctionPrototype = new FunctionPrototype(this, internalName, declaration, null); if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); return; } this.elements.set(internalName, prototype); if (namespace) { if (namespace.members.has(declaration.identifier.name)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else namespace.members.set(declaration.identifier.name, prototype); } else 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); } } 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.internalPath, queuedExports, queuedImports); } } private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map, queuedImports: QueuedImport[]): void { const internalName: string = declaration.internalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); return; } let referencedName: string = internalPath + PATH_DELIMITER + declaration.externalIdentifier.name; // resolve right away if the export exists if (this.exports.has(referencedName)) { this.elements.set(internalName, this.exports.get(referencedName)); return; } // walk already known queued exports const seen: Set = new Set(); while (queuedExports.has(referencedName)) { const queuedExport: QueuedExport = queuedExports.get(referencedName); if (queuedExport.isReExport) { if (this.exports.has(queuedExport.referencedName)) { this.elements.set(internalName, this.exports.get(referencedName)); return; } referencedName = queuedExport.referencedName; if (seen.has(queuedExport)) break; seen.add(queuedExport); } else { if (this.elements.has(queuedExport.referencedName)) { this.elements.set(internalName, this.elements.get(referencedName)); return; } break; } } // otherwise queue it const queuedImport: QueuedImport = new QueuedImport(); queuedImport.internalName = internalName; queuedImport.referencedName = referencedName; queuedImport.declaration = declaration; queuedImports.push(queuedImport); } private initializeInterface(declaration: InterfaceDeclaration, namespace: Namespace | null = null): void { const internalName: string = declaration.internalName; 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, prototype); if (namespace) { if (namespace.members.has(prototype.internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else namespace.members.set(prototype.internalName, prototype); } else 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); } 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(memberDeclaration, prototype); break; case NodeKind.METHOD: this.initializeMethod(memberDeclaration, prototype); break; default: throw new Error("unexpected interface member"); } } } private initializeNamespace(declaration: NamespaceDeclaration, parentNamespace: Namespace | null = null): 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 (parentNamespace) { if (parentNamespace.members.has(declaration.identifier.name)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else parentNamespace.members.set(declaration.identifier.name, namespace); } else if (namespace.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, namespace); } const members: Statement[] = declaration.members; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { const statement: Statement = members[i]; switch (statement.kind) { case NodeKind.CLASS: this.initializeClass(statement, namespace); break; case NodeKind.ENUM: this.initializeEnum(statement, namespace); break; case NodeKind.FUNCTION: this.initializeFunction(statement, namespace); break; case NodeKind.INTERFACE: this.initializeInterface(statement, namespace); break; case NodeKind.NAMESPACE: this.initializeNamespace(statement, namespace); break; case NodeKind.VARIABLE: this.initializeVariables(statement, namespace); break; default: throw new Error("unexpected namespace member"); } } } private initializeVariables(statement: VariableStatement, namespace: Namespace | null = null): void { const declarations: VariableDeclaration[] = statement.declarations; for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; const internalName: string = declaration.internalName; 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 (namespace) { if (namespace.members.has(declaration.identifier.name)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else namespace.members.set(declaration.identifier.name, global); } else if (global.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else this.exports.set(internalName, global); } } } } /** Resolves a {@link TypeNode} to a concrete {@link Type}. */ 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; } /** Resolves an array of type parameters to concrete types. */ 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; 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; } /** Resolves an identifier to the element is refers to. */ resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function): Element | null { const name: string = identifier.name; const local: Local | null = contextualFunction.locals.get(name); if (local) return local; let element: Element | null; if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name)) return element; if (element = this.elements.get(name)) return element; this.error(DiagnosticCode.Cannot_find_name_0, identifier.range, name); return null; } /** Resolves a property access the element it refers to. */ resolvePropertyAccess(propertyAccess: PropertyAccessExpression, contextualFunction: Function): Element | null { const expression: Expression = propertyAccess.expression; let target: Element | null = null; if (expression.kind == NodeKind.IDENTIFIER) { target = this.resolveIdentifier(expression, contextualFunction); } else if (expression.kind == NodeKind.PROPERTYACCESS) { target = this.resolvePropertyAccess(expression, contextualFunction); } else throw new Error("unexpected target kind"); if (!target) return null; const propertyName: string = propertyAccess.property.name; let member: Element | null = null; if (target.kind == ElementKind.ENUM) member = (target).members.get(propertyName); else if (target.kind == ElementKind.NAMESPACE) member = (target).members.get(propertyName); // else if (target.kind == ElementKind.CLASS_PROTOTYPE) // member = (target).members.get(propertyName); // else if (target.kind == ElementKind.CLASS) // member = (target).members.get(propertyName); if (member) return member; this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, expression.range, (expression).property.name, target.internalName); return null; } resolveElement(expression: Expression, contextualFunction: Function): Element | null { // this -> Class if (expression.kind == NodeKind.THIS) { if (contextualFunction.instanceMethodOf) return contextualFunction.instanceMethodOf; this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); return null; } // local or global name if (expression.kind == NodeKind.IDENTIFIER) { return this.resolveIdentifier(expression, contextualFunction); // static or instance property (incl. enum values) or method } else if (expression.kind == NodeKind.PROPERTYACCESS) { return this.resolvePropertyAccess(expression, contextualFunction); } throw new Error("not implemented: " + expression.kind); } } function checkGlobalDecorator(decorators: Decorator[]): string | null { for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { const decorator: Decorator = 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; } /** 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; /** Element flags. */ flags: ElementFlags = ElementFlags.NONE; /** 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(); /** Constructs a new namespace. */ constructor(program: Program, internalName: string, declaration: NamespaceDeclaration | null = null) { super(program, internalName); 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"); } } } } } /** 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); 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"); } } } } } /** An enum value. */ export class EnumValue extends Element { kind = ElementKind.ENUMVALUE; /** Declaration reference. */ declaration: EnumValueDeclaration | null; /** Parent enum. */ enum: Enum; /** 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; // built-ins have constant values } } /** A global variable. */ export class Global extends Element { kind = ElementKind.GLOBAL; /** Declaration reference. */ declaration: VariableLikeDeclarationStatement | null; /** Resolved type, if resolved. */ type: Type | null; /** 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 = null, type: Type | null = null) { super(program, internalName); 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` } } /** 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; this.initializer = initializer; } } /** A function local. */ 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) { super(program, internalName); this.index = index; this.type = type; } } /** A yet unresolved function prototype. */ export class FunctionPrototype extends Element { kind = ElementKind.FUNCTION_PROTOTYPE; /** 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); 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; } } /** 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, "", ""); let instance: Function | 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 Function(this, internalName, typeArguments, parameters, returnType, null); // TODO: class this.instances.set(instanceKey, instance); return instance; } resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null, alternativeReportNode: Node | null): Function | null { let resolvedTypeArguments: Type[] | null; if (this.isGeneric) { if (!this.declaration) throw new Error("missing declaration"); resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode); if (!resolvedTypeArguments) return null; } else { // TODO: check typeArgumentNodes being empty resolvedTypeArguments = []; } return this.resolve(resolvedTypeArguments, contextualTypeArguments); } } /** A resolved function. */ export class Function extends Element { kind = ElementKind.FUNCTION; /** Prototype reference. */ prototype: FunctionPrototype; /** Concrete type arguments. */ typeArguments: Type[]; /** Concrete function parameters. Excluding `this` if an instance method. */ parameters: Parameter[]; /** Concrete return type. */ returnType: Type; /** If a method, the concrete class it is a member of. */ instanceMethodOf: Class | null; /** Map of locals by name. */ locals: Map = new Map(); /** List of additional non-parameter locals. */ additionalLocals: Type[] = []; /** Current break context label. */ breakContext: string | null = null; /** Contextual type arguments. */ contextualTypeArguments: Map = new Map(); private nextBreakId: i32 = 0; private breakStack: i32[] | null = null; /** Constructs a new concrete function. */ constructor(prototype: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) { super(prototype.program, internalName); this.prototype = prototype; this.typeArguments = typeArguments; this.parameters = parameters; this.returnType = returnType; this.instanceMethodOf = instanceMethodOf; this.flags = prototype.flags; let localIndex: i32 = 0; if (instanceMethodOf) { this.locals.set("this", new Local(prototype.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(prototype.program, parameter.name, localIndex++, parameter.type)); } } /** Tests if this function is an instance method. */ get isInstance(): bool { return this.instanceMethodOf != null; } /** Adds a local of the specified type, with an optional name. */ 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.isInstance) localIndex++; // plus 'this' 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"); this.locals.set(name, local); } this.additionalLocals.push(type); return local; } private tempI32s: Local[] | null = null; private tempI64s: Local[] | null = null; private tempF32s: Local[] | null = null; private tempF64s: Local[] | null = null; /** Gets a free temporary local of the specified type. */ getTempLocal(type: Type): Local { let temps: Local[] | null; switch (typeToNativeType(type)) { case NativeType.I32: temps = this.tempI32s; break; case NativeType.I64: temps = this.tempI64s; break; case NativeType.F32: temps = this.tempF32s; break; case NativeType.F64: temps = this.tempF64s; break; default: throw new Error("unexpected type"); } if (temps && temps.length > 0) return temps.pop(); return this.addLocal(type); } /** Frees the temporary local for reuse. */ freeTempLocal(local: Local): void { let temps: Local[]; switch (typeToNativeType(local.type)) { case NativeType.I32: temps = this.tempI32s || (this.tempI32s = []); break; case NativeType.I64: temps = this.tempI64s || (this.tempI64s = []); break; case NativeType.F32: temps = this.tempF32s || (this.tempF32s = []); break; case NativeType.F64: temps = this.tempF64s || (this.tempF64s = []); break; default: throw new Error("unexpected type"); } temps.push(local); } /** Gets and immediately frees a temporary local of the specified type. */ getAndFreeTempLocal(type: Type): Local { let temps: Local[]; switch (typeToNativeType(type)) { case NativeType.I32: temps = this.tempI32s || (this.tempI32s = []); break; case NativeType.I64: temps = this.tempI64s || (this.tempI64s = []); break; case NativeType.F32: temps = this.tempF32s || (this.tempF32s = []); break; case NativeType.F64: temps = this.tempF64s || (this.tempF64s = []); break; default: throw new Error("unexpected type"); } if (temps.length > 0) return temps[temps.length - 1]; let local: Local = this.addLocal(type); temps.push(local); return local; } /** Enters a(nother) break context. */ enterBreakContext(): string { const id: i32 = this.nextBreakId++; if (!this.breakStack) this.breakStack = [ id ]; else this.breakStack.push(id); return this.breakContext = id.toString(10); } /** Leaves the current break context. */ leaveBreakContext(): void { assert(this.breakStack != null); const length: i32 = (this.breakStack).length; assert(length > 0); (this.breakStack).pop(); if (length > 1) { this.breakContext = (this.breakStack)[length - 2].toString(10) } else { this.breakContext = null; this.breakStack = null; } } } /** A yet unresolved instance field prototype. */ export class FieldPrototype extends Element { kind = ElementKind.FIELD_PROTOTYPE; /** Declaration reference. */ declaration: FieldDeclaration | null; /** Parent class prototype. */ classPrototype: ClassPrototype; /** 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"); } } } } } /** A resolved instance field. */ export class Field extends Element { kind = ElementKind.FIELD; /** Field prototype reference. */ prototype: FieldPrototype; /** Resolved type. */ type: Type; /** Constant integer value, if applicable. */ constantIntegerValue: I64 | null = null; /** Constant float value, if applicable. */ constantFloatValue: f64 = 0; /** Constructs a new field. */ constructor(prototype: FieldPrototype, internalName: string, type: Type) { super(prototype.program, internalName); this.flags = prototype.flags; this.type = type; } } /** A yet unresolved class prototype. */ export class ClassPrototype extends Element { kind = ElementKind.CLASS_PROTOTYPE; /** Declaration reference. */ declaration: ClassDeclaration | null; /** Resolved instances. */ instances: Map = new Map(); /** Static fields. */ staticFields: Map | null = null; /** Static methods. */ staticMethods: Map | null = null; /** Static getters. */ staticGetters: Map | null = null; /** Static setters. */ staticSetters: Map | null = null; constructor(program: Program, internalName: string, declaration: ClassDeclaration | null = null) { super(program, internalName); 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; } } resolve(typeArguments: Type[], contextualTypeArguments: Map | null): Class { const key: string = typesToString(typeArguments, "", ""); let instance: Class | 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"); } resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null, alternativeReportNode: Node | null): Class | null { let resolvedTypeArguments: Type[] | null; if (this.isGeneric) { if (!this.declaration) throw new Error("not implemented"); // generic built-in resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode); if (!resolvedTypeArguments) return null; } else { // TODO: check typeArgumentNodes being empty resolvedTypeArguments = []; } return this.resolve(resolvedTypeArguments, contextualTypeArguments); } } /** A resolved class. */ export class Class extends Element { kind = ElementKind.CLASS; /** Prototype reference. */ prototype: ClassPrototype; /** Resolved type arguments. */ typeArguments: Type[]; /** Resolved class type. */ type: Type; /** Base class, if applicable. */ base: Class | null; /** Contextual type argumentsfor fields and methods. */ contextualTypeArguments: Map = new Map(); /** Instance fields. */ instanceFields: Map | null = null; /** Instance methods. */ instanceMethods: Map | null = null; /** Instance getters. */ instanceGetters: Map | null = null; /** Instance setters. */ instanceSetters: Map | null = null; /** Constructs a new class. */ constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] = [], base: Class | null = null) { super(prototype.program, internalName); this.prototype = prototype; this.flags = prototype.flags; this.typeArguments = typeArguments; this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this); this.base = base; // inherit contextual type arguments from base class 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.prototype.declaration; if (declaration) { // irrelevant for built-ins 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]); } } toString(): string { throw new Error("not implemented"); } } /** A yet unresolved interface. */ export class InterfacePrototype extends ClassPrototype { kind = ElementKind.INTERFACE_PROTOTYPE; /** Declaration reference. */ declaration: InterfaceDeclaration | null; // more specific /** Constructs a new interface prototype. */ constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null = null) { super(program, internalName, declaration); } } /** A resolved interface. */ export class Interface extends Class { kind = ElementKind.INTERFACE; /** Prototype reference. */ prototype: InterfacePrototype; // more specific /** Base interface, if applcable. */ base: Interface | null; // more specific /** Constructs a new interface. */ constructor(prototype: InterfacePrototype, internalName: string, typeArguments: Type[] = [], base: Interface | null = null) { super(prototype, internalName, typeArguments, base); } }