Add showcase compiler test incl. respective features and fixes

This commit is contained in:
dcodeIO 2018-01-05 18:19:32 +01:00
parent 2d0f5f3087
commit 9846f6c033
15 changed files with 15362 additions and 88 deletions

View File

@ -439,11 +439,12 @@ export abstract class Node {
return stmt; return stmt;
} }
static createImportStatement(declarations: ImportDeclaration[], path: StringLiteralExpression, range: Range): ImportStatement { static createImportStatement(declarations: ImportDeclaration[] | null, path: StringLiteralExpression, range: Range): ImportStatement {
var stmt = new ImportStatement(); var stmt = new ImportStatement();
stmt.range = range; stmt.range = range;
for (var i: i32 = 0, k: i32 = (stmt.declarations = declarations).length; i < k; ++i) if (stmt.declarations = declarations)
declarations[i].parent = stmt; for (var i: i32 = 0, k: i32 = (<ImportDeclaration[]>declarations).length; i < k; ++i)
(<ImportDeclaration[]>declarations)[i].parent = stmt;
stmt.namespaceName = null; stmt.namespaceName = null;
stmt.path = path; stmt.path = path;
stmt.normalizedPath = resolvePath(normalizePath(path.value), range.source.normalizedPath); stmt.normalizedPath = resolvePath(normalizePath(path.value), range.source.normalizedPath);
@ -1709,22 +1710,20 @@ export class ImportStatement extends Statement {
internalPath: string; internalPath: string;
serialize(sb: string[]): void { serialize(sb: string[]): void {
sb.push("import ");
if (this.declarations) { if (this.declarations) {
sb.push("import {\n"); sb.push("{\n");
for (var i: i32 = 0, k: i32 = this.declarations.length; i < k; ++i) { for (var i: i32 = 0, k: i32 = this.declarations.length; i < k; ++i) {
if (i > 0) if (i > 0)
sb.push(",\n"); sb.push(",\n");
this.declarations[i].serialize(sb); this.declarations[i].serialize(sb);
} }
sb.push("\n}"); sb.push("\n} from ");
} else { } else if (this.namespaceName) {
sb.push("import * as "); sb.push("* as ");
if (this.namespaceName) this.namespaceName.serialize(sb);
this.namespaceName.serialize(sb); sb.push(" from ");
else
throw new Error("missing asterisk import identifier");
} }
sb.push(" from ");
this.path.serialize(sb); this.path.serialize(sb);
} }
} }

View File

@ -282,7 +282,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable(); return module.createUnreachable();
if ((compiler.currentType = (<Type[]>typeArguments)[0]).isAnyInteger) { if ((compiler.currentType = (<Type[]>typeArguments)[0]).isAnyInteger) {
arg0 = compiler.compileExpression(operands[0], (<Type[]>typeArguments)[0]); arg0 = compiler.compileExpression(operands[0], (<Type[]>typeArguments)[0]);
return (compiler.currentType = (<Type[]>typeArguments)[0]).isLongInteger // sic return (compiler.currentType = (<Type[]>typeArguments)[0]).isLongInteger // sic
? module.createUnary(UnaryOp.ClzI64, arg0) ? module.createUnary(UnaryOp.ClzI64, arg0)
: (<Type[]>typeArguments)[0].isSmallInteger : (<Type[]>typeArguments)[0].isSmallInteger

View File

@ -453,25 +453,26 @@ export class Compiler extends DiagnosticEmitter {
if (element.isCompiled) if (element.isCompiled)
return true; return true;
element.isCompiled = true; // members might reference each other, triggering another compile
var previousValue: EnumValue | null = null; var previousValue: EnumValue | null = null;
if (element.members) if (element.members)
for (var member of element.members.values()) { for (var member of element.members.values()) {
if (member.kind != ElementKind.ENUMVALUE) if (member.kind != ElementKind.ENUMVALUE) // happens if an enum is also a namespace
continue; continue;
var initInStart = false;
var val = <EnumValue>member; var val = <EnumValue>member;
if (val.hasConstantValue) { if (val.hasConstantValue) {
this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue)); this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue));
} else if (val.declaration) { } else if (val.declaration) {
var declaration = val.declaration; var valueDeclaration = val.declaration;
var initExpr: ExpressionRef; var initExpr: ExpressionRef;
var initInStart = false; if (valueDeclaration.value) {
if (declaration.value) { initExpr = this.compileExpression(<Expression>valueDeclaration.value, Type.i32);
initExpr = this.compileExpression(<Expression>declaration.value, Type.i32);
if (!this.module.noEmit && _BinaryenExpressionGetId(initExpr) != ExpressionId.Const) { if (!this.module.noEmit && _BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
initExpr = this.precomputeExpressionRef(initExpr); initExpr = this.precomputeExpressionRef(initExpr);
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) { if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
if (element.isConstant) if (element.isConstant)
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, declaration.range); this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, valueDeclaration.range);
initInStart = true; initInStart = true;
} }
} }
@ -486,7 +487,7 @@ export class Compiler extends DiagnosticEmitter {
this.module.createI32(1) this.module.createI32(1)
); );
if (element.isConstant) if (element.isConstant)
this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, declaration.range); this.warning(DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, valueDeclaration.range);
initInStart = true; initInStart = true;
} }
if (initInStart) { if (initInStart) {
@ -506,9 +507,11 @@ export class Compiler extends DiagnosticEmitter {
} }
} else } else
throw new Error("declaration expected"); throw new Error("declaration expected");
if (element.declaration && isModuleExport(element, element.declaration) && !initInStart)
this.module.addGlobalExport(member.internalName, member.internalName);
previousValue = <EnumValue>val; previousValue = <EnumValue>val;
} }
return element.isCompiled = true; return true;
} }
// functions // functions
@ -2167,6 +2170,8 @@ export class Compiler extends DiagnosticEmitter {
return this.module.createUnreachable(); return this.module.createUnreachable();
var element = resolved.element; var element = resolved.element;
var tempLocal: Local;
var targetExpr: ExpressionRef;
switch (element.kind) { switch (element.kind) {
case ElementKind.LOCAL: case ElementKind.LOCAL:
@ -2202,12 +2207,12 @@ export class Compiler extends DiagnosticEmitter {
return this.module.createUnreachable(); return this.module.createUnreachable();
} }
assert(resolved.targetExpression != null); assert(resolved.targetExpression != null);
var targetExpr = this.compileExpression(<Expression>resolved.targetExpression, Type.usize32); targetExpr = this.compileExpression(<Expression>resolved.targetExpression, Type.usize32);
this.currentType = select<Type>((<Field>element).type, Type.void, tee); this.currentType = select<Type>((<Field>element).type, Type.void, tee);
var elementNativeType = (<Field>element).type.toNativeType(); var elementNativeType = (<Field>element).type.toNativeType();
if (!tee) if (!tee)
return this.module.createStore((<Field>element).type.byteSize, targetExpr, valueWithCorrectType, elementNativeType, (<Field>element).memoryOffset); return this.module.createStore((<Field>element).type.byteSize, targetExpr, valueWithCorrectType, elementNativeType, (<Field>element).memoryOffset);
var tempLocal = this.currentFunction.getAndFreeTempLocal((<Field>element).type); tempLocal = this.currentFunction.getAndFreeTempLocal((<Field>element).type);
return this.module.createBlock(null, [ // TODO: simplify if valueWithCorrectType has no side effects return this.module.createBlock(null, [ // TODO: simplify if valueWithCorrectType has no side effects
this.module.createSetLocal(tempLocal.index, valueWithCorrectType), this.module.createSetLocal(tempLocal.index, valueWithCorrectType),
this.module.createStore((<Field>element).type.byteSize, targetExpr, this.module.createGetLocal(tempLocal.index, elementNativeType), elementNativeType, (<Field>element).memoryOffset), this.module.createStore((<Field>element).type.byteSize, targetExpr, this.module.createGetLocal(tempLocal.index, elementNativeType), elementNativeType, (<Field>element).memoryOffset),
@ -2221,8 +2226,15 @@ export class Compiler extends DiagnosticEmitter {
if (setterInstance) { if (setterInstance) {
assert(setterInstance.parameters.length == 1); assert(setterInstance.parameters.length == 1);
if (!tee) { if (!tee) {
this.currentType = Type.void; if (setterInstance.isInstance) {
return this.makeCall(setterInstance, [ valueWithCorrectType ]); assert(resolved.targetExpression != null);
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, select<Type>(Type.usize64, Type.usize32, this.options.target == Target.WASM64));
this.currentType = Type.void;
return this.makeCall(setterInstance, [ targetExpr, valueWithCorrectType ]);
} else {
this.currentType = Type.void;
return this.makeCall(setterInstance, [ valueWithCorrectType ]);
}
} }
var getterPrototype = (<Property>element).getterPrototype; var getterPrototype = (<Property>element).getterPrototype;
assert(getterPrototype != null); assert(getterPrototype != null);
@ -2230,10 +2242,19 @@ export class Compiler extends DiagnosticEmitter {
if (getterInstance) { if (getterInstance) {
assert(getterInstance.parameters.length == 0); assert(getterInstance.parameters.length == 0);
this.currentType = getterInstance.returnType; this.currentType = getterInstance.returnType;
return this.module.createBlock(null, [ if (setterInstance.isInstance) {
this.makeCall(setterInstance, [ valueWithCorrectType ]), assert(resolved.targetExpression != null);
this.makeCall(getterInstance) targetExpr = this.compileExpression(<Expression>resolved.targetExpression, select<Type>(Type.usize64, Type.usize32, this.options.target == Target.WASM64));
], getterInstance.returnType.toNativeType()); tempLocal = this.currentFunction.getAndFreeTempLocal(getterInstance.returnType);
return this.module.createBlock(null, [
this.makeCall(setterInstance, [ this.module.createTeeLocal(tempLocal.index, targetExpr), valueWithCorrectType ]),
this.makeCall(getterInstance, [ this.module.createGetLocal(tempLocal.index, tempLocal.type.toNativeType()) ])
], getterInstance.returnType.toNativeType());
} else
return this.module.createBlock(null, [
this.makeCall(setterInstance, [ valueWithCorrectType ]),
this.makeCall(getterInstance)
], getterInstance.returnType.toNativeType());
} }
} }
} else } else
@ -2517,6 +2538,7 @@ export class Compiler extends DiagnosticEmitter {
return this.module.createUnreachable(); return this.module.createUnreachable();
var element = resolved.element; var element = resolved.element;
var targetExpr: ExpressionRef;
switch (element.kind) { switch (element.kind) {
case ElementKind.GLOBAL: // static property case ElementKind.GLOBAL: // static property
@ -2542,8 +2564,10 @@ export class Compiler extends DiagnosticEmitter {
assert(resolved.target != null); assert(resolved.target != null);
assert(resolved.targetExpression != null); assert(resolved.targetExpression != null);
assert((<Field>element).memoryOffset >= 0); assert((<Field>element).memoryOffset >= 0);
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, select<Type>(Type.usize64, Type.usize32, this.options.target == Target.WASM64));
this.currentType = (<Field>element).type;
return this.module.createLoad((<Field>element).type.byteSize, (<Field>element).type.isSignedInteger, return this.module.createLoad((<Field>element).type.byteSize, (<Field>element).type.isSignedInteger,
this.compileExpression(<Expression>resolved.targetExpression, select<Type>(Type.usize64, Type.usize32, this.options.target == Target.WASM64)), targetExpr,
(<Field>element).type.toNativeType(), (<Field>element).type.toNativeType(),
(<Field>element).memoryOffset (<Field>element).memoryOffset
); );
@ -2556,7 +2580,11 @@ export class Compiler extends DiagnosticEmitter {
return this.module.createUnreachable(); return this.module.createUnreachable();
assert(getterInstance.parameters.length == 0); assert(getterInstance.parameters.length == 0);
this.currentType = getterInstance.returnType; this.currentType = getterInstance.returnType;
return this.makeCall(getterInstance); if (getterInstance.isInstance) {
var targetExpr = this.compileExpression(<Expression>resolved.targetExpression, select<Type>(Type.usize64, Type.usize32, this.options.target == Target.WASM64))
return this.makeCall(getterInstance, [ targetExpr ]);
} else
return this.makeCall(getterInstance);
} }
this.error(DiagnosticCode.Operation_not_supported, propertyAccess.range); this.error(DiagnosticCode.Operation_not_supported, propertyAccess.range);
return this.module.createUnreachable(); return this.module.createUnreachable();
@ -2802,11 +2830,11 @@ export class Compiler extends DiagnosticEmitter {
function isModuleExport(element: Element, declaration: DeclarationStatement): bool { function isModuleExport(element: Element, declaration: DeclarationStatement): bool {
if (!element.isExported) if (!element.isExported)
return false; return false;
if (declaration.range.source.isEntry)
return true;
var parentNode = declaration.parent; var parentNode = declaration.parent;
if (!parentNode) if (!parentNode)
return false; return false;
if (declaration.range.source.isEntry && parentNode.kind != NodeKind.NAMESPACE)
return true;
if (parentNode.kind == NodeKind.VARIABLE) if (parentNode.kind == NodeKind.VARIABLE)
if (!(parentNode = parentNode.parent)) if (!(parentNode = parentNode.parent))
return false; return false;

View File

@ -910,10 +910,11 @@ export class Parser extends DiagnosticEmitter {
} }
parseImport(tn: Tokenizer): ImportStatement | null { parseImport(tn: Tokenizer): ImportStatement | null {
// at 'import': ('{' (ImportMember (',' ImportMember)*)? '}' | '*' 'as' Identifier) 'from' StringLiteral ';'? // at 'import': ('{' (ImportMember (',' ImportMember)*)? '}' | '*' 'as' Identifier)? 'from' StringLiteral ';'?
var startPos = tn.tokenPos; var startPos = tn.tokenPos;
var members: ImportDeclaration[] | null = null; var members: ImportDeclaration[] | null = null;
var namespaceName: IdentifierExpression | null = null; var namespaceName: IdentifierExpression | null = null;
var skipFrom = false;
if (tn.skip(Token.OPENBRACE)) { if (tn.skip(Token.OPENBRACE)) {
members = new Array(); members = new Array();
if (!tn.skip(Token.CLOSEBRACE)) { if (!tn.skip(Token.CLOSEBRACE)) {
@ -940,26 +941,18 @@ export class Parser extends DiagnosticEmitter {
this.error(DiagnosticCode._0_expected, tn.range(), "as"); this.error(DiagnosticCode._0_expected, tn.range(), "as");
return null; return null;
} }
} else { } else
this.error(DiagnosticCode._0_expected, tn.range(), "{"); skipFrom = true;
return null;
} if (skipFrom || tn.skip(Token.FROM)) {
if (tn.skip(Token.FROM)) {
if (tn.skip(Token.STRINGLITERAL)) { if (tn.skip(Token.STRINGLITERAL)) {
var path = Node.createStringLiteralExpression(tn.readString(), tn.range()); var path = Node.createStringLiteralExpression(tn.readString(), tn.range());
var ret: ImportStatement; var ret: ImportStatement;
if (members) { if (namespaceName) {
if (!namespaceName) assert(!members);
ret = Node.createImportStatement(members, path, tn.range(startPos, tn.pos));
else {
assert(false);
return null;
}
} else if (namespaceName) {
ret = Node.createImportStatementWithWildcard(namespaceName, path, tn.range(startPos, tn.pos)); ret = Node.createImportStatementWithWildcard(namespaceName, path, tn.range(startPos, tn.pos));
} else { } else {
assert(false); ret = Node.createImportStatement(members, path, tn.range(startPos, tn.pos));
return null;
} }
if (!this.seenlog.has(ret.normalizedPath)) { if (!this.seenlog.has(ret.normalizedPath)) {
this.backlog.push(ret.normalizedPath); this.backlog.push(ret.normalizedPath);

View File

@ -438,6 +438,7 @@ export class Program extends DiagnosticEmitter {
else else
(<Property>propertyElement).setterPrototype = instancePrototype; (<Property>propertyElement).setterPrototype = instancePrototype;
classPrototype.instanceMembers.set(name, propertyElement); classPrototype.instanceMembers.set(name, propertyElement);
this.elements.set(internalPropertyName, propertyElement);
} }
} }
@ -614,8 +615,7 @@ export class Program extends DiagnosticEmitter {
return; return;
} }
this.error(DiagnosticCode.Operation_not_supported, statement.range); // TODO this.error(DiagnosticCode.Operation_not_supported, statement.range); // TODO
} else }
throw new Error("imports must either define members or a namespace");
} }
private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void { private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
@ -912,26 +912,27 @@ export class Program extends DiagnosticEmitter {
var name = identifier.name; var name = identifier.name;
var local = contextualFunction.locals.get(name); var local = contextualFunction.locals.get(name);
if (local) if (local)
return resolvedElement.set(local); return (resolvedElement || (resolvedElement = new ResolvedElement())).set(local);
var element: Element | null; var element: Element | null;
var namespace: Element | null; var namespace: Element | null;
// search parent namespaces if applicable // search contextual parent namespaces if applicable
if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) { if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) {
do { do {
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)) if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))
return resolvedElement.set(element); // if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)))
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
} while (namespace = namespace.namespace); } while (namespace = namespace.namespace);
} }
// search current file // search current file
if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name)) if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name))
return resolvedElement.set(element); return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
// search global scope // search global scope
if (element = this.elements.get(name)) if (element = this.elements.get(name))
return resolvedElement.set(element); return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
this.error(DiagnosticCode.Cannot_find_name_0, identifier.range, name); this.error(DiagnosticCode.Cannot_find_name_0, identifier.range, name);
return null; return null;
@ -939,13 +940,11 @@ export class Program extends DiagnosticEmitter {
/** Resolves a property access to the element it refers to. */ /** Resolves a property access to the element it refers to. */
resolvePropertyAccess(propertyAccess: PropertyAccessExpression, contextualFunction: Function): ResolvedElement | null { resolvePropertyAccess(propertyAccess: PropertyAccessExpression, contextualFunction: Function): ResolvedElement | null {
var resolved: ResolvedElement | null;
// start by resolving the lhs target (expression before the last dot) // start by resolving the lhs target (expression before the last dot)
var targetExpression = propertyAccess.expression; var targetExpression = propertyAccess.expression;
if (!(resolved = this.resolveExpression(targetExpression, contextualFunction))) if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
return null; return null;
var target = resolved.element; var target = resolvedElement.element;
// at this point we know exactly what the target is, so look up the element within // at this point we know exactly what the target is, so look up the element within
var propertyName = propertyAccess.property.name; var propertyName = propertyAccess.property.name;
@ -973,16 +972,22 @@ export class Program extends DiagnosticEmitter {
} }
resolveElementAccess(elementAccess: ElementAccessExpression, contextualFunction: Function): ResolvedElement | null { resolveElementAccess(elementAccess: ElementAccessExpression, contextualFunction: Function): ResolvedElement | null {
var resolved: ResolvedElement | null;
// start by resolving the lhs target (expression before the last dot) // start by resolving the lhs target (expression before the last dot)
var targetExpression = elementAccess.expression; var targetExpression = elementAccess.expression;
if (!(resolved = this.resolveExpression(targetExpression, contextualFunction))) if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
return null; return null;
var target = resolved.element; var target = resolvedElement.element;
// at this point we know exactly what the target is, so make sure it is an array and look up the element within switch (target.kind) {
throw new Error("not implemented"); case ElementKind.CLASS:
var type = (<Class>target).type;
if (type.classType) {
// TODO: check if array etc.
}
break;
}
this.error(DiagnosticCode.Operation_not_supported, elementAccess.range);
return null;
} }
resolveExpression(expression: Expression, contextualFunction: Function): ResolvedElement | null { resolveExpression(expression: Expression, contextualFunction: Function): ResolvedElement | null {
@ -991,13 +996,13 @@ export class Program extends DiagnosticEmitter {
case NodeKind.THIS: // -> Class case NodeKind.THIS: // -> Class
if (classType = contextualFunction.instanceMethodOf) if (classType = contextualFunction.instanceMethodOf)
return resolvedElement.set(classType); return (resolvedElement || (resolvedElement = new ResolvedElement())).set(classType);
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
return null; return null;
case NodeKind.SUPER: // -> Class case NodeKind.SUPER: // -> Class
if ((classType = contextualFunction.instanceMethodOf) && (classType = classType.base)) if ((classType = contextualFunction.instanceMethodOf) && (classType = classType.base))
return resolvedElement.set(classType); return (resolvedElement || (resolvedElement = new ResolvedElement())).set(classType);
this.error(DiagnosticCode._super_can_only_be_referenced_in_a_derived_class, expression.range); this.error(DiagnosticCode._super_can_only_be_referenced_in_a_derived_class, expression.range);
return null; return null;
@ -1009,11 +1014,9 @@ export class Program extends DiagnosticEmitter {
case NodeKind.ELEMENTACCESS: case NodeKind.ELEMENTACCESS:
return this.resolveElementAccess(<ElementAccessExpression>expression, contextualFunction); return this.resolveElementAccess(<ElementAccessExpression>expression, contextualFunction);
default:
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return null;
} }
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return null;
} }
} }
@ -1022,11 +1025,12 @@ export class ResolvedElement {
/** The target element, if a property or element access */ /** The target element, if a property or element access */
target: Element | null; target: Element | null;
/** The target element's sub-expression, if a property or element access. */ /** The target element's expression, if a property or element access. */
targetExpression: Expression | null; targetExpression: Expression | null;
/** The element being accessed. */ /** The element being accessed. */
element: Element; element: Element;
/** Clears the target and sets the resolved element. */
set(element: Element): this { set(element: Element): this {
this.target = null; this.target = null;
this.targetExpression = null; this.targetExpression = null;
@ -1034,6 +1038,7 @@ export class ResolvedElement {
return this; return this;
} }
/** Sets the resolved target in addition to the previously set element. */
withTarget(target: Element, targetExpression: Expression): this { withTarget(target: Element, targetExpression: Expression): this {
this.target = target; this.target = target;
this.targetExpression = targetExpression; this.targetExpression = targetExpression;
@ -1041,7 +1046,8 @@ export class ResolvedElement {
} }
} }
var resolvedElement = new ResolvedElement(); // Cached result structure instance
var resolvedElement: ResolvedElement | null;
/** Indicates the specific kind of an {@link Element}. */ /** Indicates the specific kind of an {@link Element}. */
export enum ElementKind { export enum ElementKind {
@ -1114,7 +1120,9 @@ export enum ElementFlags {
/** Is an abstract member. */ /** Is an abstract member. */
ABSTRACT = 1 << 16, ABSTRACT = 1 << 16,
/** Is a struct-like class with limited capabilites. */ /** Is a struct-like class with limited capabilites. */
STRUCT = 1 << 17 STRUCT = 1 << 17,
/** Has already inherited base class static members. */
HAS_STATIC_BASE_MEMBERS = 1 << 18
} }
/** Base class of all program elements. */ /** Base class of all program elements. */
@ -1810,11 +1818,22 @@ export class ClassPrototype extends Element {
this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range); this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
return null; return null;
} }
if ((this.flags & ElementFlags.HAS_STATIC_BASE_MEMBERS) == 0) { // inherit static base members once
this.flags |= ElementFlags.HAS_STATIC_BASE_MEMBERS;
if (baseClass.prototype.members) {
if (!this.members)
this.members = new Map();
for (var baseMember of baseClass.prototype.members.values())
if (!baseMember.isInstance)
this.members.set(baseMember.simpleName, baseMember);
}
}
if (baseClass.prototype.isStruct != this.isStruct) { if (baseClass.prototype.isStruct != this.isStruct) {
this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range)); this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
return null; return null;
} }
} } else
this.flags |= ElementFlags.HAS_STATIC_BASE_MEMBERS; // fwiw
// override call specific contextual type arguments if provided // override call specific contextual type arguments if provided
var i: i32, k: i32; var i: i32, k: i32;
@ -1880,8 +1899,14 @@ export class ClassPrototype extends Element {
instance.members.set(member.simpleName, methodPrototype); instance.members.set(member.simpleName, methodPrototype);
break; break;
case ElementKind.PROPERTY: // instance properties are just copied because there is nothing to partially-resolve
if (!instance.members)
instance.members = new Map();
instance.members.set(member.simpleName, member);
break;
default: default:
throw new Error("instance member expected"); throw new Error("instance member expected: " + member.kind);
} }
} }
@ -1931,11 +1956,14 @@ export class Class extends Element {
this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this); this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this);
this.base = base; this.base = base;
// inherit contextual type arguments from base class // inherit static members and contextual type arguments from base class
if (base && base.contextualTypeArguments) { if (base) {
if (!this.contextualTypeArguments) this.contextualTypeArguments = new Map(); if (base.contextualTypeArguments) {
for (var [baseName, baseType] of base.contextualTypeArguments) if (!this.contextualTypeArguments)
this.contextualTypeArguments.set(baseName, baseType); this.contextualTypeArguments = new Map();
for (var [baseName, baseType] of base.contextualTypeArguments)
this.contextualTypeArguments.set(baseName, baseType);
}
} }
// apply instance-specific contextual type arguments // apply instance-specific contextual type arguments

View File

@ -46,14 +46,12 @@ export class CArray<T> {
private constructor() {} private constructor() {}
@operator("[]") @operator("[]")
get(index: i32): T { get(index: usize): T {
assert(index >= 0); return load<T>(changetype<usize>(this) + index * sizeof<T>());
return load<T>(index * sizeof<T>());
} }
@operator("[]=") @operator("[]=")
set(index: i32, value: T): void { set(index: usize, value: T): void {
assert(index >= 0); store<T>(changetype<usize>(this) + index * sizeof<T>(), value);
store<T>(index * sizeof<T>(), value);
} }
} }

View File

@ -1,9 +1,33 @@
(module (module
(type $i (func (result i32))) (type $i (func (result i32)))
(type $v (func)) (type $v (func))
(global $enum/Implicit.ZERO i32 (i32.const 0))
(global $enum/Implicit.ONE i32 (i32.const 1))
(global $enum/Implicit.TWO i32 (i32.const 2))
(global $enum/Implicit.THREE i32 (i32.const 3))
(global $enum/Explicit.ZERO i32 (i32.const 0))
(global $enum/Explicit.ONE i32 (i32.const 1))
(global $enum/Explicit.TWO i32 (i32.const 2))
(global $enum/Explicit.THREE i32 (i32.const 3))
(global $enum/Mixed.ZERO i32 (i32.const 0))
(global $enum/Mixed.ONE i32 (i32.const 1))
(global $enum/Mixed.THREE i32 (i32.const 3))
(global $enum/Mixed.FOUR i32 (i32.const 4))
(global $enum/NonConstant.ZERO (mut i32) (i32.const 0)) (global $enum/NonConstant.ZERO (mut i32) (i32.const 0))
(global $enum/NonConstant.ONE (mut i32) (i32.const 0)) (global $enum/NonConstant.ONE (mut i32) (i32.const 0))
(memory $0 1) (memory $0 1)
(export "enum/Implicit.ZERO" (global $enum/Implicit.ZERO))
(export "enum/Implicit.ONE" (global $enum/Implicit.ONE))
(export "enum/Implicit.TWO" (global $enum/Implicit.TWO))
(export "enum/Implicit.THREE" (global $enum/Implicit.THREE))
(export "enum/Explicit.ZERO" (global $enum/Explicit.ZERO))
(export "enum/Explicit.ONE" (global $enum/Explicit.ONE))
(export "enum/Explicit.TWO" (global $enum/Explicit.TWO))
(export "enum/Explicit.THREE" (global $enum/Explicit.THREE))
(export "enum/Mixed.ZERO" (global $enum/Mixed.ZERO))
(export "enum/Mixed.ONE" (global $enum/Mixed.ONE))
(export "enum/Mixed.THREE" (global $enum/Mixed.THREE))
(export "enum/Mixed.FOUR" (global $enum/Mixed.FOUR))
(export "memory" (memory $0)) (export "memory" (memory $0))
(start $start) (start $start)
(func $start (; 0 ;) (type $v) (func $start (; 0 ;) (type $v)

View File

@ -1,9 +1,33 @@
(module (module
(type $i (func (result i32))) (type $i (func (result i32)))
(type $v (func)) (type $v (func))
(global $enum/Implicit.ZERO i32 (i32.const 0))
(global $enum/Implicit.ONE i32 (i32.const 1))
(global $enum/Implicit.TWO i32 (i32.const 2))
(global $enum/Implicit.THREE i32 (i32.const 3))
(global $enum/Explicit.ZERO i32 (i32.const 0))
(global $enum/Explicit.ONE i32 (i32.const 1))
(global $enum/Explicit.TWO i32 (i32.const 2))
(global $enum/Explicit.THREE i32 (i32.const 3))
(global $enum/Mixed.ZERO i32 (i32.const 0))
(global $enum/Mixed.ONE i32 (i32.const 1))
(global $enum/Mixed.THREE i32 (i32.const 3))
(global $enum/Mixed.FOUR i32 (i32.const 4))
(global $enum/NonConstant.ZERO (mut i32) (i32.const 0)) (global $enum/NonConstant.ZERO (mut i32) (i32.const 0))
(global $enum/NonConstant.ONE (mut i32) (i32.const 0)) (global $enum/NonConstant.ONE (mut i32) (i32.const 0))
(memory $0 1) (memory $0 1)
(export "enum/Implicit.ZERO" (global $enum/Implicit.ZERO))
(export "enum/Implicit.ONE" (global $enum/Implicit.ONE))
(export "enum/Implicit.TWO" (global $enum/Implicit.TWO))
(export "enum/Implicit.THREE" (global $enum/Implicit.THREE))
(export "enum/Explicit.ZERO" (global $enum/Explicit.ZERO))
(export "enum/Explicit.ONE" (global $enum/Explicit.ONE))
(export "enum/Explicit.TWO" (global $enum/Explicit.TWO))
(export "enum/Explicit.THREE" (global $enum/Explicit.THREE))
(export "enum/Mixed.ZERO" (global $enum/Mixed.ZERO))
(export "enum/Mixed.ONE" (global $enum/Mixed.ONE))
(export "enum/Mixed.THREE" (global $enum/Mixed.THREE))
(export "enum/Mixed.FOUR" (global $enum/Mixed.FOUR))
(export "memory" (memory $0)) (export "memory" (memory $0))
(start $start) (start $start)
(func $enum/getZero (; 0 ;) (type $i) (result i32) (func $enum/getZero (; 0 ;) (type $i) (result i32)

View File

@ -17,6 +17,18 @@
(global $enum/NonConstant.ONE (mut i32) (i32.const 0)) (global $enum/NonConstant.ONE (mut i32) (i32.const 0))
(global $HEAP_BASE i32 (i32.const 4)) (global $HEAP_BASE i32 (i32.const 4))
(memory $0 1) (memory $0 1)
(export "enum/Implicit.ZERO" (global $enum/Implicit.ZERO))
(export "enum/Implicit.ONE" (global $enum/Implicit.ONE))
(export "enum/Implicit.TWO" (global $enum/Implicit.TWO))
(export "enum/Implicit.THREE" (global $enum/Implicit.THREE))
(export "enum/Explicit.ZERO" (global $enum/Explicit.ZERO))
(export "enum/Explicit.ONE" (global $enum/Explicit.ONE))
(export "enum/Explicit.TWO" (global $enum/Explicit.TWO))
(export "enum/Explicit.THREE" (global $enum/Explicit.THREE))
(export "enum/Mixed.ZERO" (global $enum/Mixed.ZERO))
(export "enum/Mixed.ONE" (global $enum/Mixed.ONE))
(export "enum/Mixed.THREE" (global $enum/Mixed.THREE))
(export "enum/Mixed.FOUR" (global $enum/Mixed.FOUR))
(export "memory" (memory $0)) (export "memory" (memory $0))
(start $start) (start $start)
(func $enum/getZero (; 0 ;) (type $i) (result i32) (func $enum/getZero (; 0 ;) (type $i) (result i32)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

127
tests/compiler/showcase.ts Normal file
View File

@ -0,0 +1,127 @@
// This test case compiles to WebAssembly today, highlights some of the features that have been
// implemented already and gives a quick outlook on the road ahead.
// Global variables can be constant
const aConstantGlobal: i32 = 42;
// Constant globals can be exported to JS from the entry file
export const anExportedConstantGlobal: f32 = 42.0;
// Global variables can be mutable
var aMutableGlobal: i32 = 42;
// Variables can infer their type
var anInferredI32 = 42; // infers i32 by default
var anInferredI64 = 0x100000000; // infers i64 because the value doesn't fit in 32 bits
var anInferredF64 = 42.0; // infers f64 because it is notated as a float
var anInferredF32 = <f32>42.0; // infers f32 by evaluating its initializer
// Unary expressions just work
import "./unary";
// Binary expressions just work
import "./binary";
// Logical expressions just work
import "./logical";
// Several WebAssembly and some common JavaScript built-ins are supported and compile to opcodes directly
import "./builtins";
// Speaking of imports: Exports and re-exports are supported as well
export { aConstantGlobal };
export { anExportedConstantGlobal as anAliasedConstantGlobal } from "./showcase";
// Elements can be arranged in namespaces
namespace ANamespace {
export var aNamespacedGlobal: i32 = 42;
export function aNamespacedFunction(a: i32): i32 { return a; } // functions just work
}
// The compiler supports built-in assertions (--noAssert disables them globally)
assert(ANamespace.aNamespacedFunction(ANamespace.aNamespacedGlobal) == 42);
// Enums become constant globals and thus can be exported from the entry file
export enum AnEnum {
ONE = 1, // values can use explicit initializers
TWO, // or assume the previous value + 1
// or be omitted
FOUR = AnEnum.TWO + 2, // or reference other values (and remain constant through precomputation)
FIVE, // and continue from there
FOURTYTWO = aMutableGlobal, // or reference mutable values but then can't be exported
FOURTYTHREE // and even continue from there without being exported (tsc doesn't allow this)
}
assert(AnEnum.ONE == 1);
assert(AnEnum.TWO == 2);
assert(AnEnum.FOUR == 4);
assert(AnEnum.FIVE == 5);
assert(AnEnum.FOURTYTWO == 42);
assert(AnEnum.FOURTYTHREE == 43);
// In fact, there are a couple of things asc just waves through where tsc refuses to
1, 2, 3; // for example not-so-useful comma expressions
function addGeneric<T>(left: T, right: T): T {
return left + right; // or maybe-useful generic math
}
// Speaking of generics: While there is no type inference yet, it just works
addGeneric<i32>(1, 2); // compiles and calls the i32 version
addGeneric<f32>(1, 2); // compiles and calls the f32 version
clz<i64>(0x8000); // most built-ins are generic as well
// Type aliases work but must be declared in the global scope for now
type double = f64;
addGeneric<double>(1, 2); // compiles and calls the f64 version
// Speaking of lazy compilation: Stuff that's not used is considered dead code and not compiled by default
function anUnusedFunction(): void { }
// That is, unless exported from the entry file, so it is considered reachable
export function anExportedFunction(): void { }
// Or, of course, `--noTreeShaking` is specified
// As you see, while classes, strings and arrays are still in the works, pretty much everything can
// be implemented already. Here are a few more sophisitcated examples of code that'll most likely
// make it into the standard library eventually:
import "./memcpy"; // until replaced by the proposed `move_memory` intrinsic (sic.)
import "./fmod"; // for floating point modulus support, e.g., `1.5 % 1.0`
// Speaking of classes: Some preliminary work has already been done, so while we can't properly
// instantiate them yet, we can point them at some raw memory
class AClass {
static aStaticField: AClass | null = null;
aField: i32;
}
class ADerivedClass extends AClass {
aNotherField: f32;
get aWildAccessorAppears(): f32 { return this.aNotherField; }
set aWildAccessorAppears(val: f32) { this.aNotherField = val; }
}
var aClassInstance = changetype<ADerivedClass>(<usize>8);
aClassInstance.aField = 42;
aClassInstance.aNotherField = 9000;
assert(load<i32>(8) == 42);
assert(load<f32>(12) == 9000);
aClassInstance.aWildAccessorAppears = 123;
assert(aClassInstance.aWildAccessorAppears == 123);
AClass.aStaticField = aClassInstance;
assert(ADerivedClass.aStaticField == aClassInstance);
// yet that's pretty much a work in progress, until...
// Speaking of the standard library:
// Ultimately, a memory manager should still be present regardless of the GC spec because making
// everything a GC-managed object impacts performance where GC isn't necessary. TLSF appears to
// be a viable candidate because it's relatively fast and small and an ARC-variant seems like a
// good internal alternative to a general-purpose GC if we can figure out reference cycles.
// With GC (and earlier: host-bindings) it will technically be possible to declare classes whose
// instances can cross the JS/WASM boundary natively, but then aren't stored in linear memory.
// Have a nice day!
// P.S: Interested in compilers? Nothing cooler to do with your spare time? Say hi!

6394
tests/compiler/showcase.wast Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,3 +5,5 @@ import { A, B, C } from "./other";
import { A as B, C, D as E, F } from "./other"; import { A as B, C, D as E, F } from "./other";
import * as A from "./other"; import * as A from "./other";
import "./other";

View File

@ -13,3 +13,4 @@ D as E,
F F
} from "./other"; } from "./other";
import * as A from "./other"; import * as A from "./other";
import "./other";