This commit is contained in:
dcodeIO 2017-12-13 04:46:05 +01:00
parent ce57820f59
commit 7d85b0cc7f
4 changed files with 318 additions and 120 deletions

View File

@ -7,7 +7,7 @@ AssemblyScript NEXT
By compiling syntactially but not necessarily semantically valid TypeScript to [Binaryen](https://github.com/WebAssembly/binaryen) IR, the resulting module can be validated, optimized, emitted in WebAssembly text or binary format and converted to [asm.js](http://asmjs.org) as a polyfill.
The compiler itself utilizies "portable definitions" so it can be compiled to both JavaScript using `tsc` and, eventually, to WebAssembly using `asc`.
The compiler itself utilizes "portable definitions" so it can be compiled to both JavaScript using `tsc` and, eventually, to WebAssembly using `asc`.
Development status
------------------
@ -32,7 +32,7 @@ $> cd next
$> npm install
```
Author your module either using
Author your module using either
* the [assembly definitions](./std/assembly.d.ts) ([base config](./std/assembly.json)) if all you care about is targeting WebAssembly/asm.js or
* the [portable definitions](./std/portable.d.ts) ([base config](./std/portable.json)) if you also want to compile to JavaScript using `tsc`

View File

@ -3,7 +3,7 @@ import { DiagnosticCode } from "./diagnostics";
import { Node, Expression } from "./ast";
import { Type } from "./types";
import { Module, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef } from "./module";
import { Program, FunctionPrototype, Local } from "./program";
import { Program, ElementFlags, FunctionPrototype, Local } from "./program";
/** Initializes the specified program with built-in functions. */
export function initialize(program: Program): void {
@ -41,7 +41,7 @@ export function initialize(program: Program): void {
function addFunction(program: Program, name: string, isGeneric: bool = false): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isBuiltIn = true;
prototype.isGeneric = isGeneric;
if (isGeneric) prototype.isGeneric = true;
program.elements.set(name, prototype);
}

View File

@ -20,6 +20,7 @@ import {
ClassPrototype,
Class, Element,
ElementKind,
ElementFlags,
Enum,
FunctionPrototype,
Function,
@ -201,7 +202,7 @@ export class Compiler extends DiagnosticEmitter {
if (!typeRef)
typeRef = this.module.addFunctionType("v", NativeType.None, []);
this.module.setStart(
this.module.addFunction(this.startFunction.template.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody))
this.module.addFunction(this.startFunction.prototype.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody))
);
}
@ -314,11 +315,11 @@ export class Compiler extends DiagnosticEmitter {
return <Global>element;
}
compileGlobal(element: Global): bool {
if (element.isCompiled)
compileGlobal(global: Global): bool {
if (global.isCompiled)
return true;
const declaration: VariableLikeDeclarationStatement | null = element.declaration;
let type: Type | null = element.type;
const declaration: VariableLikeDeclarationStatement | null = global.declaration;
let type: Type | null = global.type;
if (!type) {
if (!declaration)
throw new Error("unexpected missing declaration");
@ -327,33 +328,33 @@ export class Compiler extends DiagnosticEmitter {
type = this.program.resolveType(declaration.type); // reports
if (!type)
return false;
element.type = type;
global.type = type;
}
if (this.module.noEmit)
return true;
const nativeType: NativeType = typeToNativeType(<Type>type);
let initializer: ExpressionRef;
let initializeInStart: bool = false;
if (element.hasConstantValue) {
if (global.hasConstantValue) {
if (type.isLongInteger)
initializer = element.constantIntegerValue ? this.module.createI64(element.constantIntegerValue.lo, element.constantIntegerValue.hi) : this.module.createI64(0, 0);
initializer = global.constantIntegerValue ? this.module.createI64(global.constantIntegerValue.lo, global.constantIntegerValue.hi) : this.module.createI64(0, 0);
else if (type.kind == TypeKind.F32)
initializer = this.module.createF32(element.constantFloatValue);
initializer = this.module.createF32(global.constantFloatValue);
else if (type.kind == TypeKind.F64)
initializer = this.module.createF64(element.constantFloatValue);
initializer = this.module.createF64(global.constantFloatValue);
else if (type.isSmallInteger) {
if (type.isSignedInteger) {
const shift: i32 = type.smallIntegerShift;
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() << shift >> shift : 0);
initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() << shift >> shift : 0);
} else
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & type.smallIntegerMask: 0);
initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() & type.smallIntegerMask: 0);
} else
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() : 0);
initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() : 0);
} else if (declaration) {
if (declaration.initializer) {
initializer = this.compileExpression(declaration.initializer, type);
if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) {
if (!element.isMutable) {
if (!global.isMutable) {
initializer = this.precomputeExpressionRef(initializer);
if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) {
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range);
@ -366,34 +367,35 @@ export class Compiler extends DiagnosticEmitter {
initializer = typeToNativeZero(this.module, type);
} else
throw new Error("unexpected missing declaration or constant value");
const internalName: string = element.internalName;
const internalName: string = global.internalName;
if (initializeInStart) {
this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, type));
this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
} else {
this.module.addGlobal(internalName, nativeType, element.isMutable, initializer);
if (!element.isMutable) {
element.hasConstantValue = true;
this.module.addGlobal(internalName, nativeType, global.isMutable, initializer);
if (!global.isMutable) {
const exprType: NativeType = _BinaryenExpressionGetType(initializer);
switch (exprType) {
case NativeType.I32:
element.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initializer), 0);
global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initializer), 0);
break;
case NativeType.I64:
element.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initializer), _BinaryenConstGetValueI64High(initializer));
global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initializer), _BinaryenConstGetValueI64High(initializer));
break;
case NativeType.F32:
element.constantFloatValue = _BinaryenConstGetValueF32(initializer);
global.constantFloatValue = _BinaryenConstGetValueF32(initializer);
break;
case NativeType.F64:
element.constantFloatValue = _BinaryenConstGetValueF64(initializer);
global.constantFloatValue = _BinaryenConstGetValueF64(initializer);
break;
default:
throw new Error("unexpected initializer type");
}
global.hasConstantValue = true;
}
}
return element.isCompiled = true;
global.isCompiled = true;
return true;
}
// enums
@ -446,8 +448,8 @@ export class Compiler extends DiagnosticEmitter {
} else {
this.module.addGlobal(val.internalName, NativeType.I32, false, initializer);
if (_BinaryenExpressionGetType(initializer) == NativeType.I32) {
val.hasConstantValue = true;
val.constantValue = _BinaryenConstGetValueI32(initializer);
val.hasConstantValue = true;
} else
throw new Error("unexpected initializer type");
}
@ -483,11 +485,11 @@ export class Compiler extends DiagnosticEmitter {
if (instance.isCompiled)
return true;
const declaration: FunctionDeclaration | null = instance.template.declaration;
const declaration: FunctionDeclaration | null = instance.prototype.declaration;
if (!declaration)
throw new Error("unexpected missing declaration");
if (instance.isDeclare) {
if (instance.isDeclared) {
if (declaration.statements) {
this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, declaration.identifier.range);
return false;
@ -502,7 +504,7 @@ export class Compiler extends DiagnosticEmitter {
// compile statements
let stmts: ExpressionRef[] | null = null;
if (!instance.isDeclare) {
if (!instance.isDeclared) {
const previousFunction: Function = this.currentFunction;
this.currentFunction = instance;
stmts = this.compileStatements(<Statement[]>declaration.statements);
@ -525,7 +527,7 @@ export class Compiler extends DiagnosticEmitter {
// create the function
const internalName: string = instance.internalName;
if (instance.isDeclare) {
if (instance.isDeclared) {
this.module.addFunctionImport(internalName, "env", declaration.identifier.name, typeRef);
} else {
this.module.addFunction(internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, <ExpressionRef[]>stmts, NativeType.None));
@ -580,7 +582,7 @@ export class Compiler extends DiagnosticEmitter {
switch (element.kind) {
case ElementKind.CLASS_PROTOTYPE:
if ((noTreeShaking || (<ClassPrototype>element).isExport) && !(<ClassPrototype>element).isGeneric)
if ((noTreeShaking || (<ClassPrototype>element).isExported) && !(<ClassPrototype>element).isGeneric)
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
break;
@ -589,7 +591,7 @@ export class Compiler extends DiagnosticEmitter {
break;
case ElementKind.FUNCTION_PROTOTYPE:
if ((noTreeShaking || (<FunctionPrototype>element).isExport) && !(<FunctionPrototype>element).isGeneric)
if ((noTreeShaking || (<FunctionPrototype>element).isExported) && !(<FunctionPrototype>element).isGeneric)
this.compileFunctionUsingTypeArguments(<FunctionPrototype>element, []);
break;
@ -1680,7 +1682,7 @@ export class Compiler extends DiagnosticEmitter {
this.compileFunction(functionInstance);
// imported function
if (functionInstance.isDeclare)
if (functionInstance.isDeclared)
return this.module.createCallImport(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType));
// internal function

View File

@ -61,6 +61,7 @@ class QueuedImport {
const noTypesYet: Map<string,Type> = new Map();
/** Represents an AssemblyScript program. */
export class Program extends DiagnosticEmitter {
/** Array of source files. */
@ -76,6 +77,7 @@ export class Program extends DiagnosticEmitter {
/** Exports of individual files by internal name. Not global exports. */
exports: Map<string,Element> = new Map();
/** Constructs a new program, optionally inheriting parser diagnostics. */
constructor(diagnostics: DiagnosticMessage[] | null = null) {
super(diagnostics);
this.sources = new Array();
@ -152,7 +154,7 @@ export class Program extends DiagnosticEmitter {
let element: Element | null;
// queued imports should be resolvable now
// queued imports should be resolvable now through traversing exports and queued exports
for (let i: i32 = 0; i < queuedImports.length;) {
const queuedImport: QueuedImport = queuedImports[i];
element = this.tryResolveImport(queuedImport.referencedName, queuedExports);
@ -165,7 +167,7 @@ export class Program extends DiagnosticEmitter {
}
}
// queued exports should be resolvable noww
// queued exports should be resolvable now that imports are finalized
for (let [exportName, queuedExport] of queuedExports) {
let currentExport: QueuedExport | null = queuedExport;
do {
@ -190,6 +192,7 @@ export class Program extends DiagnosticEmitter {
}
}
/** Tries to resolve an import by traversing exports and queued exports. */
private tryResolveImport(referencedName: string, queuedExports: Map<string,QueuedExport>): Element | null {
let element: Element | null;
do {
@ -215,7 +218,7 @@ export class Program extends DiagnosticEmitter {
}
const prototype: ClassPrototype = new ClassPrototype(this, internalName, declaration);
this.elements.set(internalName, prototype);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
if (prototype.isExported) {
if (this.exports.has(internalName))
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
else
@ -281,7 +284,7 @@ export class Program extends DiagnosticEmitter {
}
const enm: Enum = new Enum(this, internalName, declaration);
this.elements.set(internalName, enm);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
if (enm.isExported) {
if (this.exports.has(internalName))
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
else
@ -311,12 +314,10 @@ export class Program extends DiagnosticEmitter {
private initializeExport(member: ExportMember, internalPath: string | null, queuedExports: Map<string,QueuedExport>): void {
const externalName: string = member.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name;
if (this.exports.has(externalName)) {
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName);
return;
}
let referencedName: string;
// export local element
@ -393,15 +394,12 @@ export class Program extends DiagnosticEmitter {
return;
}
this.elements.set(internalName, prototype);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
if (prototype.isExported) {
if (this.exports.has(internalName))
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
else
this.exports.set(internalName, prototype);
}
if (hasModifier(ModifierKind.DECLARE, declaration.modifiers)) {
prototype.isDeclare = true;
}
}
private initializeImports(statement: ImportStatement, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
@ -459,16 +457,17 @@ export class Program extends DiagnosticEmitter {
private initializeInterface(declaration: InterfaceDeclaration): void {
const internalName: string = declaration.internalName;
const interfacePrototype: InterfacePrototype = new InterfacePrototype(this, internalName, declaration);
const prototype: InterfacePrototype = new InterfacePrototype(this, internalName, declaration);
if (this.elements.has(internalName))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
else
this.elements.set(internalName, interfacePrototype);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
this.elements.set(internalName, prototype);
if (prototype.isExported) {
if (this.exports.has(internalName))
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
else
this.exports.set(internalName, interfacePrototype);
this.exports.set(internalName, prototype);
}
const memberDeclarations: DeclarationStatement[] = declaration.members;
for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) {
@ -476,11 +475,11 @@ export class Program extends DiagnosticEmitter {
switch (memberDeclaration.kind) {
case NodeKind.FIELD:
this.initializeField(<FieldDeclaration>memberDeclaration, interfacePrototype);
this.initializeField(<FieldDeclaration>memberDeclaration, prototype);
break;
case NodeKind.METHOD:
this.initializeMethod(<MethodDeclaration>memberDeclaration, interfacePrototype);
this.initializeMethod(<MethodDeclaration>memberDeclaration, prototype);
break;
default:
@ -492,11 +491,12 @@ export class Program extends DiagnosticEmitter {
private initializeNamespace(declaration: NamespaceDeclaration): void {
const internalName: string = declaration.internalName;
const namespace: Namespace = new Namespace(this, internalName, declaration);
if (this.elements.has(internalName))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
else {
this.elements.set(internalName, namespace);
if (hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
if (namespace.isExported) {
if (this.exports.has(internalName))
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName);
else
@ -504,8 +504,8 @@ export class Program extends DiagnosticEmitter {
}
}
const members: Statement[] = declaration.members;
for (let j: i32 = 0, l: i32 = members.length; j < l; ++j) {
const statement: Statement = members[j];
for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) {
const statement: Statement = members[i];
switch (statement.kind) {
case NodeKind.CLASS:
@ -540,7 +540,6 @@ export class Program extends DiagnosticEmitter {
private initializeVariables(statement: VariableStatement, isNamespaceMember: bool = false): void {
const declarations: VariableDeclaration[] = statement.declarations;
const isExport: bool = !isNamespaceMember && hasModifier(ModifierKind.EXPORT, statement.modifiers);
for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) {
const declaration: VariableDeclaration = declarations[i];
const internalName: string = declaration.internalName;
@ -549,7 +548,7 @@ export class Program extends DiagnosticEmitter {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
else {
this.elements.set(internalName, global);
if (isExport) {
if (global.isExported) {
if (this.exports.has(internalName))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName);
else
@ -559,6 +558,7 @@ export class Program extends DiagnosticEmitter {
}
}
/** Resolves a {@link TypeNode} to a concrete {@link Type}. */
resolveType(node: TypeNode, contextualTypeArguments: Map<string,Type> | null = null, reportNotFound: bool = true): Type | null {
// resolve parameters
@ -596,6 +596,7 @@ export class Program extends DiagnosticEmitter {
return null;
}
/** Resolves {@link TypeParameter}s to concrete {@link Type}s. */
resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): Type[] | null {
const parameterCount: i32 = typeParameters.length;
const argumentCount: i32 = typeArgumentNodes ? typeArgumentNodes.length : 0;
@ -684,83 +685,193 @@ function checkGlobalDecorator(decorators: Decorator[]): string | null {
return null;
}
/** Indicates the specific kind of an {@link Element}. */
export enum ElementKind {
/** A {@link ClassPrototype}. */
CLASS_PROTOTYPE,
/** A {@link Class}. */
CLASS,
/** An {@link Enum}. */
ENUM,
/** An {@link EnumValue}. */
ENUMVALUE,
/** A {@link FieldPrototype}. */
FIELD_PROTOTYPE,
/** A {@link Field}. */
FIELD,
/** A {@link FunctionPrototype}. */
FUNCTION_PROTOTYPE,
/** A {@link Function}. */
FUNCTION,
/** A {@link Global}. */
GLOBAL,
/** An {@link InterfacePrototype}. */
INTERFACE_PROTOTYPE,
/** An {@link Interface}. */
INTERFACE,
/** A {@link Local}. */
LOCAL,
/** A {@link Namespace}. */
NAMESPACE
}
/** Indicates traits of an {@link Element}. */
export enum ElementFlags {
/** No flags set. */
NONE = 0,
/** Is compiled. */
COMPILED = 1 << 0,
/** Is an import. */
IMPORTED = 1 << 1,
/** Is an export. */
EXPORTED = 1 << 2,
/** Is built-in. */
BUILTIN = 1 << 3,
/** Is declared. */
DECLARED = 1 << 4,
/** Is generic. */
GENERIC = 1 << 5,
/** Is constant. */
CONSTANT = 1 << 6,
/** Has constant value. */
CONSTANT_VALUE = 1 << 7,
/** Is instance member. */
INSTANCE = 1 << 8,
/** Is getter. */
GETTER = 1 << 9,
/** Is setter. */
SETTER = 1 << 10
}
/** Base class of all program elements. */
export abstract class Element {
/** Specific element kind. */
kind: ElementKind;
/** Containing {@link Program}. */
program: Program;
/** Internal name referring to this element. */
internalName: string;
isCompiled: bool = false;
isImport: bool = false;
isBuiltIn: bool = false;
isDeclare: bool = false;
/** Element flags. */
flags: ElementFlags = ElementFlags.NONE;
constructor(program: Program, internalName: string) {
/** Constructs a new element, linking it to its containing {@link Program}. */
protected constructor(program: Program, internalName: string) {
this.program = program;
this.internalName = internalName;
}
/** Whether compiled or not. */
get isCompiled(): bool { return (this.flags & ElementFlags.COMPILED) != 0; }
set isCompiled(is: bool) { if (is) this.flags |= ElementFlags.COMPILED; else this.flags &= ~ElementFlags.COMPILED; }
/** Whether imported or not. */
get isImported(): bool { return (this.flags & ElementFlags.IMPORTED) != 0; }
set isImported(is: bool) { if (is) this.flags |= ElementFlags.IMPORTED; else this.flags &= ~ElementFlags.IMPORTED; }
/** Whether exported or not. */
get isExported(): bool { return (this.flags & ElementFlags.EXPORTED) != 0; }
set isExported(is: bool) { if (is) this.flags |= ElementFlags.EXPORTED; else this.flags &= ~ElementFlags.EXPORTED; }
/** Whether built-in or not. */
get isBuiltIn(): bool { return (this.flags & ElementFlags.BUILTIN) != 0; }
set isBuiltIn(is: bool) { if (is) this.flags |= ElementFlags.BUILTIN; else this.flags &= ~ElementFlags.BUILTIN; }
/** Whether declared or not. */
get isDeclared(): bool { return (this.flags & ElementFlags.DECLARED) != 0; }
set isDeclared(is: bool) { if (is) this.flags |= ElementFlags.DECLARED; else this.flags &= ~ElementFlags.DECLARED; }
/** Whether generic or not. */
get isGeneric(): bool { return (this.flags & ElementFlags.GENERIC) != 0; }
set isGeneric(is: bool) { if (is) this.flags |= ElementFlags.GENERIC; else this.flags &= ~ElementFlags.GENERIC; }
/** Whether constant or not. */
get isConstant(): bool { return (this.flags & ElementFlags.CONSTANT) != 0; }
set isConstant(is: bool) { if (is) this.flags |= ElementFlags.CONSTANT; else this.flags &= ~ElementFlags.CONSTANT; }
/** Whether mutable or not. */
get isMutable(): bool { return !(this.flags & ElementFlags.CONSTANT); } // reuses constant flag
set isMutable(is: bool) { if (is) this.flags &= ~ElementFlags.CONSTANT; else this.flags |= ElementFlags.CONSTANT; }
/** Whether this element has a constant value or not. */
get hasConstantValue(): bool { return (this.flags & ElementFlags.CONSTANT_VALUE) != 0; }
set hasConstantValue(is: bool) { if (is) this.flags |= ElementFlags.CONSTANT_VALUE; else this.flags &= ~ElementFlags.CONSTANT_VALUE; }
/** Whether an instance member or not. */
get isInstance(): bool { return (this.flags & ElementFlags.INSTANCE) != 0; }
set isInstance(is: bool) { if (is) this.flags |= ElementFlags.INSTANCE; else this.flags &= ~ElementFlags.INSTANCE; }
}
/** A namespace. Also the base class of other namespace-like program elements. */
export class Namespace extends Element {
kind = ElementKind.NAMESPACE;
/** Declaration reference. */
declaration: NamespaceDeclaration | null;
/** Member elements. */
members: Map<string,Element> = new Map();
constructor(program: Program, internalName: string, declaration: NamespaceDeclaration | null) {
/** Constructs a new namespace. */
constructor(program: Program, internalName: string, declaration: NamespaceDeclaration | null = null) {
super(program, internalName);
this.declaration = declaration;
if ((this.declaration = declaration) && this.declaration.modifiers) {
for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) {
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
default: throw new Error("unexpected modifier");
}
}
}
}
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : false; }
}
/** An enum. */
export class Enum extends Namespace {
kind = ElementKind.ENUM;
/** Declaration reference. */
declaration: EnumDeclaration | null;
/** Enum members. */
members: Map<string,EnumValue> = new Map(); // more specific
/** Constructs a new enum. */
constructor(program: Program, internalName: string, declaration: EnumDeclaration | null = null) {
super(program, internalName, null);
this.declaration = declaration;
if ((this.declaration = declaration) && this.declaration.modifiers) {
for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) {
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
case ModifierKind.CONST: this.isConstant = true; break;
default: throw new Error("unexpected modifier");
}
}
}
}
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't exports */ false; }
get isConstant(): bool { return this.declaration ? hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are const */ true; }
}
/** An enum value. */
export class EnumValue extends Element {
kind = ElementKind.ENUMVALUE;
/** Declaration reference. */
declaration: EnumValueDeclaration | null;
/** Parent enum. */
enum: Enum;
hasConstantValue: bool;
/** Constant value, if applicable. */
constantValue: i32 = 0;
constructor(enm: Enum, program: Program, internalName: string, declaration: EnumValueDeclaration | null = null) {
super(program, internalName);
this.enum = enm;
if (!(this.declaration = declaration)) this.hasConstantValue = true;
if (!(this.declaration = declaration))
this.hasConstantValue = true; // built-ins have constant values
}
}
@ -768,29 +879,50 @@ export class EnumValue extends Element {
export class Global extends Element {
kind = ElementKind.GLOBAL;
/** Declaration reference. */
declaration: VariableLikeDeclarationStatement | null;
/** Resolved type, if resolved. */
type: Type | null;
hasConstantValue: bool = false;
/** Constant integer value, if applicable. */
constantIntegerValue: I64 | null = null;
/** Constant float value, if applicable. */
constantFloatValue: f64 = 0;
constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null, type: Type | null) {
constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null = null, type: Type | null = null) {
super(program, internalName);
if (!(this.declaration = declaration)) this.hasConstantValue = true;
this.type = type; // resolved later if `null`, also updates constantKind
if (this.declaration = declaration) {
if (this.declaration.modifiers) {
for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) {
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.CONST: this.isConstant = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
default: throw new Error("unexpected modifier");
}
}
}
} else {
this.hasConstantValue = true; // built-ins have constant values
}
this.type = type; // resolved later if `null`
}
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't exports */ false; }
get isMutable(): bool { return this.declaration ? !hasModifier(ModifierKind.CONST, this.declaration.modifiers) : /* internals are immutable */ false; }
}
/** A function parameter. */
export class Parameter {
// not an Element on its own
/** Parameter name. */
name: string;
/** Parameter type. */
type: Type;
/** Parameter initializer. */
initializer: Expression | null;
/** Constructs a new function parameter. */
constructor(name: string, type: Type, initializer: Expression | null = null) {
this.name = name;
this.type = type;
@ -802,7 +934,10 @@ export class Parameter {
export class Local extends Element {
kind = ElementKind.LOCAL;
/** Local index. */
index: i32;
/** Local type. */
type: Type;
constructor(program: Program, internalName: string, index: i32, type: Type) {
@ -816,22 +951,44 @@ export class Local extends Element {
export class FunctionPrototype extends Element {
kind = ElementKind.FUNCTION_PROTOTYPE;
declaration: FunctionDeclaration | null;
classPrototype: ClassPrototype | null;
instances: Map<string,Function> = new Map();
isGeneric: bool;
/** Declaration reference. */
declaration: FunctionDeclaration | null;
/** Class prototype reference. */
classPrototype: ClassPrototype | null;
/** Resolved instances. */
instances: Map<string,Function> = new Map();
/** Constructs a new function prototype. */
constructor(program: Program, internalName: string, declaration: FunctionDeclaration | null, classPrototype: ClassPrototype | null = null) {
super(program, internalName);
this.declaration = declaration;
this.classPrototype = classPrototype;
this.isGeneric = declaration ? declaration.typeParameters.length > 0 : false; // built-ins set this
if (this.declaration = declaration) {
if (this.declaration.modifiers)
for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) {
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
case ModifierKind.GET: this.isGetter = true; break;
case ModifierKind.SET: this.isSetter = true; break;
default: throw new Error("unexpected modifier");
}
}
if (this.declaration.typeParameters.length)
this.isGeneric = true;
}
if (this.classPrototype = classPrototype) {
this.isInstance = true;
}
}
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; }
get isInstance(): bool { return this.classPrototype != null; }
get isGetter(): bool { return this.declaration ? hasModifier(ModifierKind.GET, this.declaration.modifiers) : /* internals aren't getters */ false; }
get isSetter(): bool { return this.declaration ? hasModifier(ModifierKind.SET, this.declaration.modifiers) : /* internals aren't setters */ false; }
/** Whether a getter function or not. */
get isGetter(): bool { return (this.flags & ElementFlags.GETTER) != 0; }
set isGetter(is: bool) { if (is) this.flags |= ElementFlags.GETTER; else this.flags &= ~ElementFlags.GETTER; }
/** Whether a setter function or not. */
get isSetter(): bool { return (this.flags & ElementFlags.SETTER) != 0; }
set isSetter(is: bool) { if (is) this.flags |= ElementFlags.SETTER; else this.flags &= ~ElementFlags.SETTER; }
resolve(typeArguments: Type[], contextualTypeArguments: Map<string,Type> | null): Function | null {
const instanceKey: string = typesToString(typeArguments, "", "");
@ -912,8 +1069,8 @@ export class Function extends Element {
kind = ElementKind.FUNCTION;
/** Underlying function template. */
template: FunctionPrototype;
/** Prototype reference. */
prototype: FunctionPrototype;
/** Concrete type arguments. */
typeArguments: Type[];
/** Concrete function parameters. Excluding `this` if an instance method. */
@ -937,13 +1094,12 @@ export class Function extends Element {
/** Constructs a new concrete function. */
constructor(prototype: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) {
super(prototype.program, internalName);
this.template = prototype;
this.prototype = prototype;
this.typeArguments = typeArguments;
this.parameters = parameters;
this.returnType = returnType;
this.instanceMethodOf = instanceMethodOf;
this.isBuiltIn = prototype.isBuiltIn;
this.isDeclare = prototype.isDeclare;
this.flags = prototype.flags;
let localIndex: i32 = 0;
if (instanceMethodOf) {
this.locals.set("this", new Local(prototype.program, "this", localIndex++, instanceMethodOf.type));
@ -964,7 +1120,7 @@ export class Function extends Element {
// if it has a name, check previously as this method will throw otherwise
let localIndex = this.parameters.length + this.additionalLocals.length;
if (this.isInstance) localIndex++; // plus 'this'
const local: Local = new Local(this.template.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type);
const local: Local = new Local(this.prototype.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type);
if (name) {
if (this.locals.has(<string>name))
throw new Error("unexpected duplicate local name");
@ -1050,30 +1206,45 @@ export class Function extends Element {
export class FieldPrototype extends Element {
kind = ElementKind.FIELD_PROTOTYPE;
/** Declaration reference. */
declaration: FieldDeclaration | null;
/** Parent class prototype. */
classPrototype: ClassPrototype;
constructor(classPrototype: ClassPrototype, internalName: string, declaration: FieldDeclaration | null) {
/** Constructs a new field prototype. */
constructor(classPrototype: ClassPrototype, internalName: string, declaration: FieldDeclaration | null = null) {
super(classPrototype.program, internalName);
this.classPrototype = classPrototype;
if ((this.declaration = declaration) && this.declaration.modifiers) {
for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) {
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.EXPORT: this.isExported = true; break;
default: throw new Error("unexpected modifier");
}
}
}
}
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; }
}
/** A resolved instance field. */
export class Field extends Element {
kind = ElementKind.FIELD;
template: FieldPrototype;
/** Field prototype reference. */
prototype: FieldPrototype;
/** Resolved type. */
type: Type;
hasConstantValue: bool = false;
/** Constant integer value, if applicable. */
constantIntegerValue: I64 | null = null;
/** Constant float value, if applicable. */
constantFloatValue: f64 = 0;
constructor(template: FieldPrototype, internalName: string, type: Type) {
super(template.program, internalName);
if (!this.template.declaration) this.hasConstantValue = true;
/** Constructs a new field. */
constructor(prototype: FieldPrototype, internalName: string, type: Type) {
super(prototype.program, internalName);
this.flags = prototype.flags;
this.type = type;
}
}
@ -1082,19 +1253,31 @@ export class Field extends Element {
export class ClassPrototype extends Namespace {
kind = ElementKind.CLASS_PROTOTYPE;
/** Declaration reference. */
declaration: ClassDeclaration | null;
/** Resolved instances. */
instances: Map<string,Class>;
isGeneric: bool;
constructor(program: Program, internalName: string, declaration: ClassDeclaration | null = null) {
super(program, internalName, null);
this.declaration = declaration;
if (this.declaration = declaration) {
if (this.declaration.modifiers) {
for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) {
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
default: throw new Error("unexpected modifier");
}
}
}
if (this.declaration.typeParameters.length)
this.isGeneric = true;
}
this.instances = new Map();
this.isGeneric = declaration ? declaration.typeParameters.length > 0 : false; // builtins can set this
}
get isExport(): bool { return this.declaration ? hasModifier(ModifierKind.EXPORT, this.declaration.modifiers) : /* internals aren't file-level exports */ false; }
resolve(typeArguments: Type[], contextualTypeArguments: Map<string,Type> | null): Class {
const key: string = typesToString(typeArguments, "", "");
let instance: Class | null = <Class | null>this.instances.get(key);
@ -1125,20 +1308,26 @@ export class ClassPrototype extends Namespace {
export class Class extends Namespace {
kind = ElementKind.CLASS;
declaration: ClassDeclaration | null;
template: ClassPrototype;
/** Prototype reference. */
prototype: ClassPrototype;
/** Resolved type arguments. */
typeArguments: Type[];
base: Class | null;
/** Resolved class type. */
type: Type;
/** Base class, if applicable. */
base: Class | null;
contextualTypeArguments: Map<string,Type> = new Map();
constructor(template: ClassPrototype, internalName: string, typeArguments: Type[], base: Class | null) {
super(template.program, internalName, template.declaration);
this.template = template;
/** Constructs a new class. */
constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] = [], base: Class | null = null) {
super(prototype.program, internalName, prototype.declaration);
this.prototype = prototype;
this.flags = prototype.flags;
this.typeArguments = typeArguments;
this.base = base;
this.type = (template.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this);
this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this);
// inherit base class contextual type arguments
if (base)
@ -1146,7 +1335,7 @@ export class Class extends Namespace {
this.contextualTypeArguments.set(name, type);
// apply instance-specific contextual type arguments
const declaration: ClassDeclaration | null = this.template.declaration;
const declaration: ClassDeclaration | null = this.prototype.declaration;
if (declaration) { // irrelevant for built-ins
const typeParameters: TypeParameter[] = declaration.typeParameters;
if (typeParameters.length != typeArguments.length)
@ -1165,9 +1354,12 @@ export class Class extends Namespace {
export class InterfacePrototype extends ClassPrototype {
kind = ElementKind.INTERFACE_PROTOTYPE;
/** Declaration reference. */
declaration: InterfaceDeclaration | null;
constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null) {
/** Constructs a new interface prototype. */
constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null = null) {
super(program, internalName, declaration);
}
}
@ -1176,10 +1368,14 @@ export class InterfacePrototype extends ClassPrototype {
export class Interface extends Class {
kind = ElementKind.INTERFACE;
template: InterfacePrototype;
/** Prototype reference. */
prototype: InterfacePrototype;
/** Base interface, if applcable. */
base: Interface | null;
constructor(template: InterfacePrototype, internalName: string, typeArguments: Type[], base: Interface | null) {
super(template, internalName, typeArguments, base);
/** Constructs a new interface. */
constructor(prototype: InterfacePrototype, internalName: string, typeArguments: Type[] = [], base: Interface | null = null) {
super(prototype, internalName, typeArguments, base);
}
}