2017-09-28 13:08:25 +02:00
|
|
|
import { Target } from "./compiler";
|
2017-10-19 18:55:27 +02:00
|
|
|
import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER } from "./constants";
|
2017-12-02 01:14:15 +01:00
|
|
|
import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, DiagnosticCategory } from "./diagnostics";
|
2017-10-19 18:55:27 +02:00
|
|
|
import { Type, typesToString } from "./types";
|
|
|
|
import { I64 } from "./util";
|
2017-09-28 13:08:25 +02:00
|
|
|
import {
|
|
|
|
|
2017-10-02 12:52:15 +02:00
|
|
|
ModifierKind,
|
2017-09-28 13:08:25 +02:00
|
|
|
Node,
|
|
|
|
NodeKind,
|
2017-10-02 12:52:15 +02:00
|
|
|
Source,
|
2017-10-19 18:55:27 +02:00
|
|
|
Range,
|
|
|
|
|
|
|
|
TypeNode,
|
|
|
|
Expression,
|
|
|
|
IdentifierExpression,
|
|
|
|
LiteralExpression,
|
|
|
|
LiteralKind,
|
|
|
|
PropertyAccessExpression,
|
|
|
|
StringLiteralExpression,
|
2017-09-28 13:08:25 +02:00
|
|
|
|
|
|
|
ClassDeclaration,
|
|
|
|
DeclarationStatement,
|
2017-10-19 18:55:27 +02:00
|
|
|
DecoratorStatement,
|
2017-09-28 13:08:25 +02:00
|
|
|
EnumDeclaration,
|
|
|
|
EnumValueDeclaration,
|
2017-09-29 17:25:02 +02:00
|
|
|
ExportMember,
|
|
|
|
ExportStatement,
|
2017-09-28 13:08:25 +02:00
|
|
|
FieldDeclaration,
|
|
|
|
FunctionDeclaration,
|
|
|
|
ImportDeclaration,
|
|
|
|
ImportStatement,
|
|
|
|
InterfaceDeclaration,
|
|
|
|
MethodDeclaration,
|
2017-10-19 18:55:27 +02:00
|
|
|
Modifier,
|
2017-09-28 13:08:25 +02:00
|
|
|
NamespaceDeclaration,
|
|
|
|
Statement,
|
2017-10-19 18:55:27 +02:00
|
|
|
TypeParameter,
|
|
|
|
VariableLikeDeclarationStatement,
|
2017-09-28 13:08:25 +02:00
|
|
|
VariableDeclaration,
|
2017-10-11 17:03:22 +02:00
|
|
|
VariableStatement,
|
|
|
|
|
2017-12-02 01:14:15 +01:00
|
|
|
hasModifier,
|
|
|
|
mangleInternalName
|
2017-09-28 13:08:25 +02:00
|
|
|
|
|
|
|
} from "./ast";
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
class QueuedExport {
|
2017-12-02 01:14:15 +01:00
|
|
|
isReExport: bool;
|
2017-10-11 17:03:22 +02:00
|
|
|
referencedName: string;
|
2017-09-29 17:25:02 +02:00
|
|
|
member: ExportMember;
|
|
|
|
}
|
|
|
|
|
|
|
|
class QueuedImport {
|
2017-10-02 12:52:15 +02:00
|
|
|
internalName: string;
|
2017-12-02 01:14:15 +01:00
|
|
|
referencedName: string;
|
2017-09-29 17:25:02 +02:00
|
|
|
declaration: ImportDeclaration;
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
const reusableTypesStub: Map<string,Type> = new Map();
|
2017-10-07 14:29:43 +02:00
|
|
|
|
2017-09-28 13:08:25 +02:00
|
|
|
export class Program extends DiagnosticEmitter {
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
/** Array of source files. */
|
2017-09-28 13:08:25 +02:00
|
|
|
sources: Source[];
|
2017-10-19 18:55:27 +02:00
|
|
|
/** Diagnostic offset used where sequentially obtaining the next diagnostic. */
|
2017-09-28 13:08:25 +02:00
|
|
|
diagnosticsOffset: i32 = 0;
|
2017-10-19 18:55:27 +02:00
|
|
|
/** WASM target. */
|
|
|
|
target: Target = Target.WASM32; // set on initialization
|
|
|
|
/** Elements by internal name. */
|
|
|
|
elements: Map<string,Element> = new Map();
|
|
|
|
/** Types by internal name. */
|
|
|
|
types: Map<string,Type> = reusableTypesStub;
|
|
|
|
/** Exports of individual files by internal name. Not global exports. */
|
|
|
|
exports: Map<string,Element> = new Map();
|
2017-09-28 13:08:25 +02:00
|
|
|
|
|
|
|
constructor(diagnostics: DiagnosticMessage[] | null = null) {
|
|
|
|
super(diagnostics);
|
|
|
|
this.sources = new Array();
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
/** Initializes the program and its elements prior to compilation. */
|
|
|
|
initialize(target: Target = Target.WASM32): void {
|
2017-09-28 13:08:25 +02:00
|
|
|
this.target = target;
|
2017-12-01 02:08:03 +01:00
|
|
|
|
|
|
|
initializeBuiltins(this);
|
2017-09-28 13:08:25 +02:00
|
|
|
|
2017-10-02 12:52:15 +02:00
|
|
|
const queuedExports: Map<string,QueuedExport> = new Map();
|
|
|
|
const queuedImports: QueuedImport[] = new Array();
|
2017-09-28 13:08:25 +02:00
|
|
|
|
2017-10-11 17:03:22 +02:00
|
|
|
// build initial lookup maps of internal names to declarations
|
2017-09-28 13:08:25 +02:00
|
|
|
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(<ClassDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.ENUM:
|
|
|
|
this.initializeEnum(<EnumDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
case NodeKind.EXPORT:
|
2017-10-02 12:52:15 +02:00
|
|
|
this.initializeExports(<ExportStatement>statement, queuedExports);
|
2017-09-29 17:25:02 +02:00
|
|
|
break;
|
|
|
|
|
2017-09-28 13:08:25 +02:00
|
|
|
case NodeKind.FUNCTION:
|
|
|
|
this.initializeFunction(<FunctionDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.IMPORT:
|
2017-10-02 12:52:15 +02:00
|
|
|
this.initializeImports(<ImportStatement>statement, queuedExports, queuedImports);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.INTERFACE:
|
|
|
|
this.initializeInterface(<InterfaceDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.NAMESPACE:
|
|
|
|
this.initializeNamespace(<NamespaceDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.VARIABLE:
|
|
|
|
this.initializeVariables(<VariableStatement>statement);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 01:14:15 +01:00
|
|
|
let element: Element | null;
|
|
|
|
|
|
|
|
// queued imports should be resolvable now
|
|
|
|
for (let i: i32 = 0; i < queuedImports.length;) {
|
|
|
|
const queuedImport: QueuedImport = queuedImports[i];
|
|
|
|
element = this.tryResolveImport(queuedImport.referencedName, queuedExports);
|
|
|
|
if (element) {
|
|
|
|
this.elements.set(queuedImport.internalName, element);
|
|
|
|
queuedImports.splice(i, 1);
|
|
|
|
} else {
|
|
|
|
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedImport.declaration.range, (<ImportStatement>queuedImport.declaration.parent).path.value, queuedImport.declaration.externalIdentifier.name);
|
|
|
|
++i;
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 01:14:15 +01:00
|
|
|
// queued exports should be resolvable noww
|
|
|
|
for (let [exportName, queuedExport] of queuedExports) {
|
|
|
|
let currentExport: QueuedExport | null = queuedExport;
|
|
|
|
do {
|
|
|
|
if (currentExport.isReExport) {
|
|
|
|
element = <Element | null>this.exports.get(currentExport.referencedName);
|
|
|
|
if (element) {
|
|
|
|
this.exports.set(exportName, element);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
currentExport = <QueuedExport | null>queuedExports.get(currentExport.referencedName);
|
|
|
|
if (!currentExport)
|
|
|
|
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.member.externalIdentifier.range, (<StringLiteralExpression>(<ExportStatement>queuedExport.member.parent).path).value, queuedExport.member.externalIdentifier.name);
|
|
|
|
} else {
|
|
|
|
element = <Element | null>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);
|
2017-09-29 17:25:02 +02:00
|
|
|
break;
|
|
|
|
}
|
2017-12-02 01:14:15 +01:00
|
|
|
} while (currentExport);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 01:14:15 +01:00
|
|
|
private tryResolveImport(referencedName: string, queuedExports: Map<string,QueuedExport>): Element | null {
|
|
|
|
let element: Element | null;
|
|
|
|
do {
|
|
|
|
element = <Element | null>this.exports.get(referencedName);
|
|
|
|
if (element)
|
|
|
|
return element;
|
|
|
|
const queuedExport: QueuedExport | null = <QueuedExport | null>queuedExports.get(referencedName);
|
|
|
|
if (!queuedExport)
|
|
|
|
return null;
|
|
|
|
if (queuedExport.isReExport) {
|
|
|
|
referencedName = queuedExport.referencedName;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return <Element | null>this.elements.get(queuedExport.referencedName);
|
|
|
|
} while (true);
|
|
|
|
}
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
private initializeClass(declaration: ClassDeclaration): void {
|
2017-10-11 17:03:22 +02:00
|
|
|
const internalName: string = declaration.internalName;
|
2017-11-17 14:33:51 +01:00
|
|
|
if (this.elements.has(internalName)) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
|
2017-11-17 14:33:51 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const prototype: ClassPrototype = new ClassPrototype(this, internalName, declaration);
|
|
|
|
this.elements.set(internalName, prototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
|
|
|
|
if (this.exports.has(internalName))
|
2017-11-17 14:33:51 +01:00
|
|
|
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
|
2017-10-19 18:55:27 +02:00
|
|
|
else
|
2017-11-17 14:33:51 +01:00
|
|
|
this.exports.set(internalName, prototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
const memberDeclarations: DeclarationStatement[] = declaration.members;
|
|
|
|
for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) {
|
|
|
|
const memberDeclaration: DeclarationStatement = memberDeclarations[j];
|
2017-11-17 14:33:51 +01:00
|
|
|
if (memberDeclaration.kind == NodeKind.FIELD)
|
|
|
|
this.initializeField(<FieldDeclaration>memberDeclaration, prototype);
|
|
|
|
else if (memberDeclaration.kind == NodeKind.METHOD)
|
|
|
|
this.initializeMethod(<MethodDeclaration>memberDeclaration, prototype);
|
|
|
|
else
|
|
|
|
throw new Error("unexpected class member");
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
private initializeField(declaration: FieldDeclaration, classPrototype: ClassPrototype): void {
|
2017-10-19 18:55:27 +02:00
|
|
|
const name: string = declaration.identifier.name;
|
2017-11-17 14:33:51 +01:00
|
|
|
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);
|
2017-10-19 18:55:27 +02:00
|
|
|
} else {
|
2017-11-17 14:33:51 +01:00
|
|
|
const field: FieldPrototype = new FieldPrototype(classPrototype, internalName, declaration);
|
|
|
|
classPrototype.members.set(name, field);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void {
|
2017-10-19 18:55:27 +02:00
|
|
|
let name: string = declaration.identifier.name;
|
2017-11-17 14:33:51 +01:00
|
|
|
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;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-17 14:33:51 +01:00
|
|
|
classPrototype.members.set(name, func);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
private initializeEnum(declaration: EnumDeclaration): void {
|
2017-10-11 17:03:22 +02:00
|
|
|
const internalName: string = declaration.internalName;
|
2017-11-17 14:33:51 +01:00
|
|
|
if (this.elements.has(internalName)) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
|
2017-11-17 14:33:51 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const enm: Enum = new Enum(this, internalName, declaration);
|
|
|
|
this.elements.set(internalName, enm);
|
|
|
|
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
|
|
|
|
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);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-11-17 14:33:51 +01:00
|
|
|
const values: EnumValueDeclaration[] = declaration.members;
|
|
|
|
for (let i: i32 = 0, k: i32 = values.length; i < k; ++i)
|
|
|
|
this.initializeEnumValue(values[i], enm);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
private initializeEnumValue(declaration: EnumValueDeclaration, enm: Enum): void {
|
|
|
|
const name: string = declaration.identifier.name;
|
|
|
|
const internalName: string = declaration.internalName;
|
2017-11-17 14:33:51 +01:00
|
|
|
if (enm.members.has(name)) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
|
2017-11-17 14:33:51 +01:00
|
|
|
return;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-11-17 14:33:51 +01:00
|
|
|
const value: EnumValue = new EnumValue(enm, this, internalName, declaration);
|
|
|
|
enm.members.set(name, value);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
|
2017-10-02 12:52:15 +02:00
|
|
|
private initializeExports(statement: ExportStatement, queuedExports: Map<string,QueuedExport>): void {
|
2017-09-29 17:25:02 +02:00
|
|
|
const members: ExportMember[] = statement.members;
|
|
|
|
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i)
|
2017-10-19 18:55:27 +02:00
|
|
|
this.initializeExport(members[i], statement.internalPath, queuedExports);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
private initializeExport(member: ExportMember, internalPath: string | null, queuedExports: Map<string,QueuedExport>): void {
|
2017-12-02 01:14:15 +01:00
|
|
|
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);
|
2017-11-17 14:33:51 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-12-02 01:14:15 +01:00
|
|
|
|
|
|
|
let referencedName: string;
|
|
|
|
|
|
|
|
// export local element
|
2017-11-17 14:33:51 +01:00
|
|
|
if (internalPath == null) {
|
2017-12-02 01:14:15 +01:00
|
|
|
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, <Element>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
|
2017-11-17 14:33:51 +01:00
|
|
|
} else {
|
2017-12-02 01:14:15 +01:00
|
|
|
referencedName = (<string>internalPath) + PATH_DELIMITER + member.externalIdentifier.name;
|
|
|
|
|
|
|
|
// resolve right away if the export exists
|
|
|
|
if (this.exports.has(referencedName)) {
|
|
|
|
this.exports.set(externalName, <Element>this.exports.get(referencedName));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// walk already known queued exports
|
|
|
|
const seen: Set<QueuedExport> = new Set();
|
|
|
|
while (queuedExports.has(referencedName)) {
|
|
|
|
const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(referencedName);
|
|
|
|
if (queuedExport.isReExport) {
|
|
|
|
if (this.exports.has(queuedExport.referencedName)) {
|
|
|
|
this.exports.set(externalName, <Element>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, <Element>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);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
private initializeFunction(declaration: FunctionDeclaration): void {
|
2017-10-11 17:03:22 +02:00
|
|
|
const internalName: string = declaration.internalName;
|
2017-11-17 14:33:51 +01:00
|
|
|
const prototype: FunctionPrototype = new FunctionPrototype(this, internalName, declaration, null);
|
|
|
|
if (this.elements.has(internalName)) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
|
2017-11-17 14:33:51 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.elements.set(internalName, prototype);
|
|
|
|
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
|
|
|
|
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);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-10-02 12:52:15 +02:00
|
|
|
private initializeImports(statement: ImportStatement, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
|
2017-09-28 13:08:25 +02:00
|
|
|
const members: ImportDeclaration[] = statement.declarations;
|
2017-09-29 17:25:02 +02:00
|
|
|
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
|
|
|
|
const declaration: ImportDeclaration = members[i];
|
2017-10-19 18:55:27 +02:00
|
|
|
this.initializeImport(declaration, statement.internalPath, queuedExports, queuedImports);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
|
2017-12-02 01:14:15 +01:00
|
|
|
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, <Element>this.exports.get(referencedName));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// walk already known queued exports
|
2017-10-02 12:52:15 +02:00
|
|
|
const seen: Set<QueuedExport> = new Set();
|
2017-12-02 01:14:15 +01:00
|
|
|
while (queuedExports.has(referencedName)) {
|
|
|
|
const queuedExport: QueuedExport = <QueuedExport>queuedExports.get(referencedName);
|
|
|
|
if (queuedExport.isReExport) {
|
|
|
|
if (this.exports.has(queuedExport.referencedName)) {
|
|
|
|
this.elements.set(internalName, <Element>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, <Element>this.elements.get(referencedName));
|
|
|
|
return;
|
|
|
|
}
|
2017-10-02 12:52:15 +02:00
|
|
|
break;
|
2017-12-02 01:14:15 +01:00
|
|
|
}
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
2017-12-02 01:14:15 +01:00
|
|
|
|
|
|
|
// otherwise queue it
|
|
|
|
const queuedImport: QueuedImport = new QueuedImport();
|
|
|
|
queuedImport.internalName = internalName;
|
|
|
|
queuedImport.referencedName = referencedName;
|
|
|
|
queuedImport.declaration = declaration;
|
|
|
|
queuedImports.push(queuedImport);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
private initializeInterface(declaration: InterfaceDeclaration): void {
|
2017-10-11 17:03:22 +02:00
|
|
|
const internalName: string = declaration.internalName;
|
2017-11-17 14:33:51 +01:00
|
|
|
const interfacePrototype: InterfacePrototype = new InterfacePrototype(this, internalName, declaration);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (this.elements.has(internalName))
|
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
|
|
|
|
else
|
2017-11-17 14:33:51 +01:00
|
|
|
this.elements.set(internalName, interfacePrototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
|
|
|
|
if (this.exports.has(internalName))
|
2017-11-17 14:33:51 +01:00
|
|
|
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
|
2017-10-19 18:55:27 +02:00
|
|
|
else
|
2017-11-17 14:33:51 +01:00
|
|
|
this.exports.set(internalName, interfacePrototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
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) {
|
2017-09-28 13:08:25 +02:00
|
|
|
|
|
|
|
case NodeKind.FIELD:
|
2017-11-17 14:33:51 +01:00
|
|
|
this.initializeField(<FieldDeclaration>memberDeclaration, interfacePrototype);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.METHOD:
|
2017-11-17 14:33:51 +01:00
|
|
|
this.initializeMethod(<MethodDeclaration>memberDeclaration, interfacePrototype);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error("unexpected interface member");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
private initializeNamespace(declaration: NamespaceDeclaration): void {
|
2017-10-11 17:03:22 +02:00
|
|
|
const internalName: string = declaration.internalName;
|
2017-11-17 14:33:51 +01:00
|
|
|
const namespace: Namespace = new Namespace(this, internalName, declaration);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (this.elements.has(internalName))
|
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
|
|
|
|
else {
|
2017-11-17 14:33:51 +01:00
|
|
|
this.elements.set(internalName, namespace);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
|
|
|
|
if (this.exports.has(internalName))
|
2017-11-17 14:33:51 +01:00
|
|
|
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
|
2017-10-19 18:55:27 +02:00
|
|
|
else
|
2017-11-17 14:33:51 +01:00
|
|
|
this.exports.set(internalName, namespace);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
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) {
|
|
|
|
|
|
|
|
case NodeKind.CLASS:
|
|
|
|
this.initializeClass(<ClassDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.ENUM:
|
|
|
|
this.initializeEnum(<EnumDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.FUNCTION:
|
|
|
|
this.initializeFunction(<FunctionDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.INTERFACE:
|
|
|
|
this.initializeInterface(<InterfaceDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.NAMESPACE:
|
|
|
|
this.initializeNamespace(<NamespaceDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NodeKind.VARIABLE:
|
2017-09-29 17:25:02 +02:00
|
|
|
this.initializeVariables(<VariableStatement>statement, true);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error("unexpected namespace member");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-02 12:52:15 +02:00
|
|
|
private initializeVariables(statement: VariableStatement, isNamespaceMember: bool = false): void {
|
|
|
|
const declarations: VariableDeclaration[] = statement.declarations;
|
|
|
|
const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers);
|
2017-10-07 14:29:43 +02:00
|
|
|
for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) {
|
2017-09-28 13:08:25 +02:00
|
|
|
const declaration: VariableDeclaration = declarations[i];
|
2017-10-11 17:03:22 +02:00
|
|
|
const internalName: string = declaration.internalName;
|
2017-10-19 18:55:27 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
resolveType(node: TypeNode, contextualTypeArguments: Map<string,Type> | 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] = <Type>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 = <Type | null>contextualTypeArguments.get(globalName);
|
|
|
|
if (placeholderType)
|
|
|
|
return placeholderType;
|
|
|
|
}
|
|
|
|
|
|
|
|
let type: Type | null;
|
|
|
|
|
|
|
|
// check file-global type
|
|
|
|
if (type = <Type | null>this.types.get(node.range.source.internalPath + PATH_DELIMITER + globalName))
|
|
|
|
return type;
|
|
|
|
|
|
|
|
// check program-global type
|
|
|
|
if (type = <Type | null>this.types.get(globalName))
|
|
|
|
return type;
|
|
|
|
|
|
|
|
if (reportNotFound)
|
|
|
|
this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName);
|
|
|
|
|
|
|
|
return null;
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): Type[] | null {
|
2017-10-19 18:55:27 +02:00
|
|
|
const parameterCount: i32 = typeParameters.length;
|
2017-11-20 23:39:50 +01:00
|
|
|
const argumentCount: i32 = typeArgumentNodes ? typeArgumentNodes.length : 0;
|
2017-10-19 18:55:27 +02:00
|
|
|
if (parameterCount != argumentCount) {
|
|
|
|
if (argumentCount)
|
2017-11-20 23:39:50 +01:00
|
|
|
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, Range.join((<TypeNode[]>typeArgumentNodes)[0].range, (<TypeNode[]>typeArgumentNodes)[argumentCount - 1].range), parameterCount.toString(10), argumentCount.toString(10));
|
2017-10-19 18:55:27 +02:00
|
|
|
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) {
|
2017-11-20 23:39:50 +01:00
|
|
|
const type: Type | null = this.resolveType((<TypeNode[]>typeArgumentNodes)[i], contextualTypeArguments, true); // reports
|
2017-10-19 18:55:27 +02:00
|
|
|
if (!type)
|
|
|
|
return null;
|
|
|
|
// TODO: check extendsType
|
|
|
|
typeArguments[i] = type;
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
return typeArguments;
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
resolveElement(expression: Expression, contextualFunction: Function): Element | null {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
// this -> Class
|
2017-10-19 18:55:27 +02:00
|
|
|
if (expression.kind == NodeKind.THIS) {
|
|
|
|
if (contextualFunction.instanceMethodOf)
|
2017-11-17 14:33:51 +01:00
|
|
|
return contextualFunction.instanceMethodOf;
|
2017-10-19 18:55:27 +02:00
|
|
|
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
let ret: Element;
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
// local or global name
|
|
|
|
if (expression.kind == NodeKind.IDENTIFIER) {
|
|
|
|
const name: string = (<IdentifierExpression>expression).name;
|
|
|
|
const local: Local | null = <Local | null>contextualFunction.locals.get(name);
|
|
|
|
if (local)
|
|
|
|
return local;
|
|
|
|
const fileGlobalElement: Element | null = <Element | null>this.elements.get(expression.range.source.internalPath + PATH_DELIMITER + name);
|
|
|
|
if (fileGlobalElement)
|
|
|
|
return fileGlobalElement;
|
|
|
|
const programGlobalElement: Element | null = <Element | null>this.elements.get(name);
|
|
|
|
if (programGlobalElement)
|
|
|
|
return programGlobalElement;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.error(DiagnosticCode.Cannot_find_name_0, expression.range, name);
|
2017-10-19 18:55:27 +02:00
|
|
|
return null;
|
|
|
|
|
|
|
|
// static or instance property (incl. enum values) or method
|
|
|
|
} else if (expression.kind == NodeKind.PROPERTYACCESS) {
|
2017-11-17 14:33:51 +01:00
|
|
|
const target: Element | null = this.resolveElement((<PropertyAccessExpression>expression).expression, contextualFunction); // reports
|
2017-10-19 18:55:27 +02:00
|
|
|
if (!target)
|
|
|
|
return null;
|
2017-11-17 14:33:51 +01:00
|
|
|
const propertyName: string = (<PropertyAccessExpression>expression).property.name;
|
2017-11-20 23:39:50 +01:00
|
|
|
let member: Element | null = null;
|
2017-11-17 14:33:51 +01:00
|
|
|
if (target.kind == ElementKind.ENUM)
|
|
|
|
member = <EnumValue | null>(<Enum>target).members.get(propertyName);
|
|
|
|
else if (target.kind == ElementKind.CLASS_PROTOTYPE)
|
|
|
|
member = <Element | null>(<ClassPrototype>target).members.get(propertyName);
|
|
|
|
else if (target.kind == ElementKind.CLASS)
|
|
|
|
member = <Element | null>(<Class>target).members.get(propertyName);
|
|
|
|
else if (target.kind == ElementKind.NAMESPACE)
|
|
|
|
member = <Element | null>(<Namespace>target).members.get(propertyName);
|
|
|
|
if (member)
|
|
|
|
return member;
|
|
|
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, expression.range, (<PropertyAccessExpression>expression).property.name, target.internalName);
|
|
|
|
return null;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
throw new Error("not implemented: " + expression.kind);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 && (<IdentifierExpression>expression).name == "global") {
|
|
|
|
if (args.length) {
|
|
|
|
const firstArg: Expression = args[0];
|
|
|
|
if (firstArg.kind == NodeKind.LITERAL && (<LiteralExpression>firstArg).literalKind == LiteralKind.STRING)
|
|
|
|
return (<StringLiteralExpression>firstArg).value;
|
|
|
|
} else
|
|
|
|
return ""; // instead inherits declaration identifier
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum ElementKind {
|
2017-11-17 14:33:51 +01:00
|
|
|
CLASS_PROTOTYPE,
|
2017-10-19 18:55:27 +02:00
|
|
|
CLASS,
|
|
|
|
ENUM,
|
|
|
|
ENUMVALUE,
|
2017-11-17 14:33:51 +01:00
|
|
|
FIELD_PROTOTYPE,
|
|
|
|
FIELD,
|
|
|
|
FUNCTION_PROTOTYPE,
|
2017-10-19 18:55:27 +02:00
|
|
|
FUNCTION,
|
|
|
|
GLOBAL,
|
2017-11-17 14:33:51 +01:00
|
|
|
INTERFACE_PROTOTYPE,
|
2017-10-19 18:55:27 +02:00
|
|
|
INTERFACE,
|
|
|
|
LOCAL,
|
|
|
|
NAMESPACE
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** Base class of all program elements. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export abstract class Element {
|
|
|
|
|
|
|
|
kind: ElementKind;
|
|
|
|
program: Program;
|
|
|
|
internalName: string;
|
2017-12-01 02:08:03 +01:00
|
|
|
isCompiled: bool = false;
|
|
|
|
isImport: bool = false;
|
|
|
|
isBuiltin: bool = false;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
constructor(program: Program, internalName: string) {
|
|
|
|
this.program = program;
|
|
|
|
this.internalName = internalName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A namespace. Also the base class of other namespace-like program elements. */
|
|
|
|
export class Namespace extends Element {
|
|
|
|
|
|
|
|
kind = ElementKind.NAMESPACE;
|
|
|
|
declaration: NamespaceDeclaration | null;
|
|
|
|
members: Map<string,Element> = 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; }
|
|
|
|
}
|
|
|
|
|
|
|
|
/** An enum. */
|
|
|
|
export class Enum extends Namespace {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
kind = ElementKind.ENUM;
|
|
|
|
declaration: EnumDeclaration | null;
|
2017-11-17 14:33:51 +01:00
|
|
|
members: Map<string,EnumValue> = new Map(); // more specific
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
constructor(program: Program, internalName: string, declaration: EnumDeclaration | null = null) {
|
2017-11-17 14:33:51 +01:00
|
|
|
super(program, internalName, null);
|
2017-10-19 18:55:27 +02:00
|
|
|
this.declaration = declaration;
|
|
|
|
}
|
|
|
|
|
|
|
|
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; }
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** An enum value. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export class EnumValue extends Element {
|
|
|
|
|
|
|
|
kind = ElementKind.ENUMVALUE;
|
|
|
|
declaration: EnumValueDeclaration | null;
|
|
|
|
enum: Enum;
|
2017-11-17 14:33:51 +01:00
|
|
|
hasConstantValue: bool;
|
2017-10-19 18:55:27 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A global variable. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export class Global extends Element {
|
|
|
|
|
|
|
|
kind = ElementKind.GLOBAL;
|
|
|
|
declaration: VariableLikeDeclarationStatement | null;
|
|
|
|
type: Type | null;
|
|
|
|
hasConstantValue: bool = false;
|
2017-11-17 14:33:51 +01:00
|
|
|
constantIntegerValue: I64 | null = null;
|
2017-10-19 18:55:27 +02:00
|
|
|
constantFloatValue: f64 = 0;
|
|
|
|
|
|
|
|
constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null, type: Type | null) {
|
|
|
|
super(program, internalName);
|
|
|
|
if (!(this.declaration = declaration)) this.hasConstantValue = true;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.type = type; // resolved later if `null`, also updates constantKind
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't exports */ false; }
|
2017-12-02 18:37:59 +01:00
|
|
|
get isMutable(): bool { return this.declaration ? !hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are immutable */ false; }
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A function parameter. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export class Parameter {
|
|
|
|
|
|
|
|
name: string;
|
|
|
|
type: Type;
|
2017-11-20 23:39:50 +01:00
|
|
|
initializer: Expression | null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
constructor(name: string, type: Type, initializer: Expression | null = null) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.name = name;
|
|
|
|
this.type = type;
|
2017-11-20 23:39:50 +01:00
|
|
|
this.initializer = initializer;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A function local. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A yet unresolved function prototype. */
|
|
|
|
export class FunctionPrototype extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.FUNCTION_PROTOTYPE;
|
2017-10-19 18:55:27 +02:00
|
|
|
declaration: FunctionDeclaration | null;
|
2017-11-17 14:33:51 +01:00
|
|
|
classPrototype: ClassPrototype | null;
|
|
|
|
instances: Map<string,Function> = new Map();
|
2017-10-19 18:55:27 +02:00
|
|
|
isGeneric: bool;
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
constructor(program: Program, internalName: string, declaration: FunctionDeclaration | null, classPrototype: ClassPrototype | null = null) {
|
2017-10-19 18:55:27 +02:00
|
|
|
super(program, internalName);
|
|
|
|
this.declaration = declaration;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.classPrototype = classPrototype;
|
2017-10-19 18:55:27 +02:00
|
|
|
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; }
|
2017-11-17 14:33:51 +01:00
|
|
|
get isInstance(): bool { return this.classPrototype != null; }
|
2017-10-19 18:55:27 +02:00
|
|
|
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; }
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
resolve(typeArguments: Type[], contextualTypeArguments: Map<string,Type> | null): Function | null {
|
2017-10-19 18:55:27 +02:00
|
|
|
const instanceKey: string = typesToString(typeArguments, "", "");
|
2017-11-17 14:33:51 +01:00
|
|
|
let instance: Function | null = <Function | null>this.instances.get(instanceKey);
|
2017-10-19 18:55:27 +02:00
|
|
|
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<string,Type> | 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>typeNode, contextualTypeArguments, true); // reports
|
|
|
|
if (type) {
|
|
|
|
parameters[i] = new Parameter(declaration.parameters[i].identifier.name, type);
|
|
|
|
parameterTypes[i] = <Type>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>typeNode, contextualTypeArguments, true); // reports
|
|
|
|
if (type)
|
|
|
|
returnType = <Type>type;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
} else
|
|
|
|
return null; // TODO: infer type? (currently reported by parser)
|
|
|
|
|
|
|
|
let internalName: string = this.internalName;
|
|
|
|
if (instanceKey.length)
|
|
|
|
internalName += "<" + instanceKey + ">";
|
2017-11-17 14:33:51 +01:00
|
|
|
instance = new Function(this, internalName, typeArguments, parameters, returnType, null); // TODO: class
|
2017-10-19 18:55:27 +02:00
|
|
|
this.instances.set(instanceKey, instance);
|
|
|
|
return instance;
|
|
|
|
}
|
2017-11-20 23:39:50 +01:00
|
|
|
|
|
|
|
resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null, alternativeReportNode: Node | null): Function | null {
|
|
|
|
let resolvedTypeArguments: Type[] | null;
|
|
|
|
if (this.isGeneric) {
|
|
|
|
if (!this.declaration)
|
2017-12-01 02:08:03 +01:00
|
|
|
throw new Error("missing declaration");
|
2017-11-20 23:39:50 +01:00
|
|
|
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);
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A resolved function. */
|
|
|
|
export class Function extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.FUNCTION;
|
2017-11-20 23:39:50 +01:00
|
|
|
|
|
|
|
/** Underlying function template. */
|
2017-11-17 14:33:51 +01:00
|
|
|
template: FunctionPrototype;
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Concrete type arguments. */
|
2017-10-19 18:55:27 +02:00
|
|
|
typeArguments: Type[];
|
2017-11-26 04:03:28 +01:00
|
|
|
/** Concrete function parameters. Excluding `this` if an instance method. */
|
2017-10-19 18:55:27 +02:00
|
|
|
parameters: Parameter[];
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Concrete return type. */
|
2017-10-19 18:55:27 +02:00
|
|
|
returnType: Type;
|
2017-11-20 23:39:50 +01:00
|
|
|
/** If a method, the concrete class it is a member of. */
|
2017-11-17 14:33:51 +01:00
|
|
|
instanceMethodOf: Class | null;
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Map of locals by name. */
|
2017-10-19 18:55:27 +02:00
|
|
|
locals: Map<string,Local> = new Map();
|
2017-11-20 23:39:50 +01:00
|
|
|
/** List of additional non-parameter locals. */
|
|
|
|
additionalLocals: Type[] = [];
|
|
|
|
/** Current break context label. */
|
2017-10-19 18:55:27 +02:00
|
|
|
breakContext: string | null = null;
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Contextual type arguments. */
|
2017-10-19 18:55:27 +02:00
|
|
|
contextualTypeArguments: Map<string,Type> = new Map();
|
|
|
|
|
|
|
|
private breakMajor: i32 = 0;
|
|
|
|
private breakMinor: i32 = 0;
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Constructs a new concrete function. */
|
2017-12-01 02:08:03 +01:00
|
|
|
constructor(prototype: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) {
|
|
|
|
super(prototype.program, internalName);
|
|
|
|
this.template = prototype;
|
2017-10-19 18:55:27 +02:00
|
|
|
this.typeArguments = typeArguments;
|
|
|
|
this.parameters = parameters;
|
|
|
|
this.returnType = returnType;
|
|
|
|
this.instanceMethodOf = instanceMethodOf;
|
2017-12-01 02:08:03 +01:00
|
|
|
this.isBuiltin = prototype.isBuiltin;
|
2017-10-19 18:55:27 +02:00
|
|
|
let localIndex: i32 = 0;
|
|
|
|
if (instanceMethodOf) {
|
2017-12-01 02:08:03 +01:00
|
|
|
this.locals.set("this", new Local(prototype.program, "this", localIndex++, instanceMethodOf.type));
|
2017-10-19 18:55:27 +02:00
|
|
|
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];
|
2017-12-01 02:08:03 +01:00
|
|
|
this.locals.set(parameter.name, new Local(prototype.program, parameter.name, localIndex++, parameter.type));
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Tests if this function is an instance method. */
|
2017-10-19 18:55:27 +02:00
|
|
|
get isInstance(): bool { return this.instanceMethodOf != null; }
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Adds a local of the specified type, with an optional name. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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;
|
2017-11-26 04:03:28 +01:00
|
|
|
if (this.isInstance) localIndex++; // plus 'this'
|
2017-10-19 18:55:27 +02:00
|
|
|
const local: Local = new Local(this.template.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type);
|
|
|
|
if (name) {
|
|
|
|
if (this.locals.has(<string>name))
|
|
|
|
throw new Error("unexpected duplicate local name");
|
|
|
|
this.locals.set(<string>name, local);
|
|
|
|
}
|
|
|
|
this.additionalLocals.push(type);
|
|
|
|
return local;
|
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Enters a(nother) break context. */
|
2017-10-19 18:55:27 +02:00
|
|
|
enterBreakContext(): string {
|
|
|
|
if (!this.breakMinor)
|
|
|
|
this.breakMajor++;
|
|
|
|
return this.breakContext = this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10);
|
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Leaves the current break context. */
|
2017-10-19 18:55:27 +02:00
|
|
|
leaveBreakContext(): void {
|
|
|
|
if (--this.breakMinor < 0)
|
|
|
|
throw new Error("unexpected unbalanced break context");
|
|
|
|
if (this.breakMinor == 0 && !--this.breakMajor)
|
|
|
|
this.breakContext = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A yet unresolved instance field prototype. */
|
|
|
|
export class FieldPrototype extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.FIELD_PROTOTYPE;
|
|
|
|
declaration: FieldDeclaration | null;
|
|
|
|
classPrototype: ClassPrototype;
|
|
|
|
|
|
|
|
constructor(classPrototype: ClassPrototype, internalName: string, declaration: FieldDeclaration | null) {
|
|
|
|
super(classPrototype.program, internalName);
|
|
|
|
this.classPrototype = classPrototype;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
type: Type;
|
|
|
|
hasConstantValue: bool = false;
|
|
|
|
constantIntegerValue: I64 | null = null;
|
|
|
|
constantFloatValue: f64 = 0;
|
|
|
|
|
|
|
|
constructor(template: FieldPrototype, internalName: string, type: Type) {
|
|
|
|
super(template.program, internalName);
|
|
|
|
if (!this.template.declaration) this.hasConstantValue = true;
|
|
|
|
this.type = type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** A yet unresolved class prototype. */
|
|
|
|
export class ClassPrototype extends Namespace {
|
|
|
|
|
|
|
|
kind = ElementKind.CLASS_PROTOTYPE;
|
2017-10-19 18:55:27 +02:00
|
|
|
declaration: ClassDeclaration | null;
|
2017-11-17 14:33:51 +01:00
|
|
|
instances: Map<string,Class>;
|
2017-10-19 18:55:27 +02:00
|
|
|
isGeneric: bool;
|
|
|
|
|
|
|
|
constructor(program: Program, internalName: string, declaration: ClassDeclaration | null = null) {
|
|
|
|
super(program, internalName, null);
|
|
|
|
this.declaration = declaration;
|
|
|
|
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; }
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
resolve(typeArguments: Type[], contextualTypeArguments: Map<string,Type> | null): Class {
|
2017-10-19 18:55:27 +02:00
|
|
|
const key: string = typesToString(typeArguments, "", "");
|
2017-11-17 14:33:51 +01:00
|
|
|
let instance: Class | null = <Class | null>this.instances.get(key);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (instance)
|
|
|
|
return instance;
|
|
|
|
if (!this.declaration)
|
|
|
|
throw new Error("unexpected instantiation of internal class");
|
|
|
|
throw new Error("not implemented");
|
|
|
|
}
|
2017-11-20 23:39:50 +01:00
|
|
|
|
|
|
|
resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null, alternativeReportNode: Node | null): Class | null {
|
|
|
|
let resolvedTypeArguments: Type[] | null;
|
|
|
|
if (this.isGeneric) {
|
|
|
|
if (!this.declaration)
|
|
|
|
throw new Error("not implemented"); // generic builtin
|
|
|
|
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);
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A resolved class. */
|
|
|
|
export class Class extends Namespace {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.CLASS;
|
|
|
|
declaration: ClassDeclaration | null;
|
|
|
|
template: ClassPrototype;
|
2017-10-19 18:55:27 +02:00
|
|
|
typeArguments: Type[];
|
2017-11-17 14:33:51 +01:00
|
|
|
base: Class | null;
|
2017-10-19 18:55:27 +02:00
|
|
|
type: Type;
|
|
|
|
|
|
|
|
contextualTypeArguments: Map<string,Type> = new Map();
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
constructor(template: ClassPrototype, internalName: string, typeArguments: Type[], base: Class | null) {
|
|
|
|
super(template.program, internalName, template.declaration);
|
2017-10-19 18:55:27 +02:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
2017-12-01 02:08:03 +01:00
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
throw new Error("not implemented");
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A yet unresvoled interface. */
|
|
|
|
export class InterfacePrototype extends ClassPrototype {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.INTERFACE_PROTOTYPE;
|
2017-10-19 18:55:27 +02:00
|
|
|
declaration: InterfaceDeclaration | null;
|
|
|
|
|
|
|
|
constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null) {
|
|
|
|
super(program, internalName, declaration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A resolved interface. */
|
|
|
|
export class Interface extends Class {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.INTERFACE;
|
|
|
|
template: InterfacePrototype;
|
|
|
|
base: Interface | null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
constructor(template: InterfacePrototype, internalName: string, typeArguments: Type[], base: Interface | null) {
|
2017-10-19 18:55:27 +02:00
|
|
|
super(template, internalName, typeArguments, base);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
}
|
2017-12-01 02:08:03 +01:00
|
|
|
|
|
|
|
function initializeBuiltins(program: Program): void {
|
|
|
|
|
|
|
|
// types
|
|
|
|
|
|
|
|
program.types = new Map([
|
|
|
|
["i8", Type.i8],
|
|
|
|
["i16", Type.i16],
|
|
|
|
["i32", Type.i32],
|
|
|
|
["i64", Type.i64],
|
|
|
|
["isize", program.target == Target.WASM64 ? Type.isize64 : Type.isize32],
|
|
|
|
["u8", Type.u8],
|
|
|
|
["u16", Type.u16],
|
|
|
|
["u32", Type.u32],
|
|
|
|
["u64", Type.u64],
|
|
|
|
["usize", program.target == Target.WASM64 ? Type.usize64 : Type.usize32],
|
|
|
|
["bool", Type.bool],
|
|
|
|
["f32", Type.f32],
|
|
|
|
["f64", Type.f64],
|
|
|
|
["void", Type.void]
|
|
|
|
]);
|
|
|
|
|
|
|
|
// functions
|
|
|
|
|
|
|
|
const genericInt: Type[] = [ Type.i32, Type.i64 ];
|
|
|
|
const genericFloat: Type[] = [ Type.f32, Type.f64 ];
|
|
|
|
|
|
|
|
addGenericUnaryBuiltin(program, "clz", genericInt);
|
|
|
|
addGenericUnaryBuiltin(program, "ctz", genericInt);
|
|
|
|
addGenericUnaryBuiltin(program, "popcnt", genericInt);
|
|
|
|
addGenericBinaryBuiltin(program, "rotl", genericInt);
|
|
|
|
addGenericBinaryBuiltin(program, "rotr", genericInt);
|
|
|
|
|
|
|
|
addGenericUnaryBuiltin(program, "abs", genericFloat);
|
|
|
|
addGenericUnaryBuiltin(program, "ceil", genericFloat);
|
|
|
|
addGenericUnaryBuiltin(program, "copysign", genericFloat);
|
|
|
|
addGenericUnaryBuiltin(program, "floor", genericFloat);
|
|
|
|
addGenericBinaryBuiltin(program, "max", genericFloat);
|
|
|
|
addGenericBinaryBuiltin(program, "min", genericFloat);
|
|
|
|
addGenericUnaryBuiltin(program, "nearest", genericFloat);
|
|
|
|
addGenericUnaryBuiltin(program, "sqrt", genericFloat);
|
|
|
|
addGenericUnaryBuiltin(program, "trunc", genericFloat);
|
|
|
|
|
|
|
|
addBuiltin(program, "current_memory", [], Type.i32);
|
|
|
|
addBuiltin(program, "grow_memory", [ Type.i32 ], Type.i32);
|
|
|
|
addBuiltin(program, "unreachable", [], Type.void);
|
|
|
|
|
|
|
|
addGenericUnaryTestBuiltin(program, "isNaN", genericFloat);
|
|
|
|
addGenericUnaryTestBuiltin(program, "isFinite", genericFloat);
|
|
|
|
|
|
|
|
// TODO: load, store, sizeof
|
|
|
|
// sizeof, for example, has varying Ts but really shouldn't provide an instance for each class
|
|
|
|
}
|
|
|
|
|
|
|
|
function addBuiltin(program: Program, name: string, parameterTypes: Type[], returnType: Type) {
|
|
|
|
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
|
|
|
|
prototype.isGeneric = false;
|
|
|
|
prototype.isBuiltin = true;
|
|
|
|
const k: i32 = parameterTypes.length;
|
|
|
|
const parameters: Parameter[] = new Array(k);
|
|
|
|
for (let i: i32 = 0; i < k; ++i)
|
|
|
|
parameters[i] = new Parameter("arg" + i, parameterTypes[i], null);
|
|
|
|
prototype.instances.set("", new Function(prototype, name, [], parameters, Type.bool, null));
|
|
|
|
}
|
|
|
|
|
|
|
|
function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]): void {
|
|
|
|
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
|
|
|
|
prototype.isGeneric = true;
|
|
|
|
prototype.isBuiltin = true;
|
|
|
|
for (let i: i32 = 0, k = types.length; i < k; ++i) {
|
|
|
|
const typeName: string = types[i].toString();
|
|
|
|
prototype.instances.set(typeName, new Function(prototype, name + "<" + typeName + ">", [ types[i] ], [ new Parameter("value", types[i], null) ], types[i], null));
|
|
|
|
}
|
|
|
|
program.elements.set(name, prototype);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]): void {
|
|
|
|
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
|
|
|
|
prototype.isGeneric = true;
|
|
|
|
prototype.isBuiltin = true;
|
|
|
|
for (let i: i32 = 0, k = types.length; i < k; ++i) {
|
|
|
|
const typeName: string = types[i].toString();
|
|
|
|
prototype.instances.set(typeName, new Function(prototype, name + "<" + typeName + ">", [ types[i], types[i] ], [ new Parameter("left", types[i], null), new Parameter("right", types[i], null) ], types[i], null));
|
|
|
|
}
|
|
|
|
program.elements.set(name, prototype);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[]): void {
|
|
|
|
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
|
|
|
|
prototype.isGeneric = true;
|
|
|
|
prototype.isBuiltin = true;
|
|
|
|
for (let i: i32 = 0, k = types.length; i < k; ++i) {
|
|
|
|
const typeName: string = types[i].toString();
|
|
|
|
prototype.instances.set(typeName, new Function(prototype, name + "<" + typeName + ">", [ types[i] ], [ new Parameter("value", types[i], null) ], Type.bool, null));
|
|
|
|
}
|
|
|
|
program.elements.set(name, prototype);
|
|
|
|
}
|