diff --git a/src/ast.ts b/src/ast.ts index 4a363664..65210b8b 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -351,6 +351,31 @@ export abstract class Node { if (stmt.arguments = args) for (var i: i32 = 0, k: i32 = (args).length; i < k; ++i) (args)[i].parent = stmt; + if (expression.kind == NodeKind.IDENTIFIER) { + switch ((expression).name) { + + case "global": + stmt.decoratorKind = DecoratorKind.GLOBAL; + break; + + case "operator": + stmt.decoratorKind = DecoratorKind.OPERATOR; + break; + + case "struct": + stmt.decoratorKind = DecoratorKind.STRUCT; + break; + + case "size": + stmt.decoratorKind = DecoratorKind.SIZE; + break; + + default: + stmt.decoratorKind = DecoratorKind.CUSTOM; + break; + } + } else + stmt.decoratorKind = DecoratorKind.CUSTOM; return stmt; } @@ -1333,6 +1358,15 @@ export class ContinueStatement extends Statement { } } +/** Built-in decorator kinds. */ +export const enum DecoratorKind { + CUSTOM, + GLOBAL, + OPERATOR, + STRUCT, + SIZE +} + /** Depresents a decorator. */ export class Decorator extends Statement { @@ -1342,6 +1376,8 @@ export class Decorator extends Statement { name: Expression; /** Argument expressions. */ arguments: Expression[] | null; + /** Built-in kind, if applicable. */ + decoratorKind: DecoratorKind; serialize(sb: string[]): void { sb.push("@"); @@ -2153,7 +2189,7 @@ export function hasModifier(kind: ModifierKind, modifiers: Modifier[] | null): b } /** Gets a specific decorator within the specified decorators, if present. */ -export function getDecorator(name: string, decorators: Decorator[] | null): Decorator | null { +export function getFirstDecorator(name: string, decorators: Decorator[] | null): Decorator | null { if (decorators) for (var i = 0, k = decorators.length; i < k; ++i) { var decorator = decorators[i]; @@ -2166,7 +2202,7 @@ export function getDecorator(name: string, decorators: Decorator[] | null): Deco /** Tests if a specific decorator is present within the specified decorators. */ export function hasDecorator(name: string, decorators: Decorator[] | null): bool { - return getDecorator(name, decorators) != null; + return getFirstDecorator(name, decorators) != null; } /** Serializes the specified node to its TypeScript representation. */ diff --git a/src/program.ts b/src/program.ts index d0de37e2..d9883ae7 100644 --- a/src/program.ts +++ b/src/program.ts @@ -38,11 +38,17 @@ import { TypeNode, TypeParameter, Decorator, + DecoratorKind, Expression, + ElementAccessExpression, IdentifierExpression, + LiteralExpression, + LiteralKind, PropertyAccessExpression, StringLiteralExpression, + SuperExpression, + ThisExpression, CallExpression, NewExpression, @@ -68,9 +74,7 @@ import { hasDecorator, hasModifier, mangleInternalName, - ElementAccessExpression, - ThisExpression, - SuperExpression + getFirstDecorator } from "./ast"; import { @@ -354,6 +358,7 @@ export class Program extends DiagnosticEmitter { private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void { var name = declaration.name.name; var internalName = declaration.internalName; + var instancePrototype: FunctionPrototype | null = null; // static methods become global functions if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) { @@ -382,12 +387,56 @@ export class Program extends DiagnosticEmitter { } } else classPrototype.instanceMembers = new Map(); - var instancePrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype); + instancePrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype); // if (classPrototype.isStruct && instancePrototype.isAbstract) { // this.error( Structs cannot declare abstract methods. ); // } classPrototype.instanceMembers.set(name, instancePrototype); } + + // handle operator annotations. operators are instance methods taking a second argument of the + // instance's type. return values vary depending on the operation. + if (declaration.decorators) { + for (var i = 0, k = declaration.decorators.length; i < k; ++i) { + var decorator = declaration.decorators[i]; + if (decorator.decoratorKind == DecoratorKind.OPERATOR) { + if (!instancePrototype) { + this.error(DiagnosticCode.Operation_not_supported, decorator.range); + continue; + } + var numArgs = decorator.arguments && decorator.arguments.length || 0; + if (numArgs == 1) { + var firstArg = (decorator.arguments)[0]; + if (firstArg.kind == NodeKind.LITERAL && (firstArg).literalKind == LiteralKind.STRING) { + switch ((firstArg).value) { + + case "[]": + classPrototype.opIndexedGet = instancePrototype; + break; + + case "[]=": + classPrototype.opIndexedSet = instancePrototype; + break; + + case "+": + classPrototype.opConcat = instancePrototype; + break; + + case "==": + classPrototype.opEquals = instancePrototype; + break; + + default: // TBD: does it make sense to provide more, even though not JS/TS-compatible? + this.error(DiagnosticCode.Operation_not_supported, firstArg.range); + } + } else + this.error(DiagnosticCode.String_literal_expected, firstArg.range); + } else + this.error(DiagnosticCode.Expected_0_arguments_but_got_1, decorator.range, "1", numArgs.toString(0)); + } else if (decorator.decoratorKind != DecoratorKind.CUSTOM) // methods support @operator only + this.error(DiagnosticCode.Operation_not_supported, decorator.range); + } + } } private initializeAccessor(declaration: MethodDeclaration, classPrototype: ClassPrototype, isGetter: bool): void { @@ -1449,6 +1498,7 @@ export class FunctionPrototype extends Element { } // resolve parameters + // TODO: 'this' type k = declaration.parameters.length; var parameters = new Array(k); var parameterTypes = new Array(k); @@ -1466,6 +1516,7 @@ export class FunctionPrototype extends Element { } // resolve return type + // TODO: 'this' type var returnType: Type; if (this.isSetter) { returnType = Type.void; // not annotated @@ -1770,6 +1821,15 @@ export class ClassPrototype extends Element { /** Instance member prototypes. */ instanceMembers: Map | null = null; + /** Overloaded indexed get method, if any. */ + opIndexedGet: FunctionPrototype | null; + /** Overloaded indexed set method, if any. */ + opIndexedSet: FunctionPrototype | null; + /** Overloaded concatenation method, if any. */ + opConcat: FunctionPrototype | null; + /** Overloaded equality comparison method, if any. */ + opEquals: FunctionPrototype | null; + constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) { super(program, simpleName, internalName); if (this.declaration = declaration) { diff --git a/std/assembly/array.ts b/std/assembly/array.ts index a9e010af..67c90aa4 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -18,13 +18,13 @@ export class Array { } @operator("[]") - get(index: i32): T { + private __get(index: i32): T { assert(index > 0 && index < this.capacity); throw new Error("not implemented"); } @operator("[]=") - set(index: i32, value: T): void { + private __set(index: i32, value: T): void { assert(index > 0 && index < this.capacity); throw new Error("not implemented"); } @@ -46,12 +46,12 @@ export class CArray { private constructor() {} @operator("[]") - get(index: usize): T { + private __get(index: usize): T { return load(changetype(this) + index * sizeof()); } @operator("[]=") - set(index: usize, value: T): void { + private __set(index: usize, value: T): void { store(changetype(this) + index * sizeof(), value); } } diff --git a/std/assembly/string.ts b/std/assembly/string.ts index 8c03211f..38521c43 100644 --- a/std/assembly/string.ts +++ b/std/assembly/string.ts @@ -13,6 +13,7 @@ export class String { this.length = lenght; } + @operator("[]") charAt(pos: i32): String { assert(this != null); return pos < 0 || pos >= this.length ? EMPTY @@ -39,7 +40,7 @@ export class String { } @operator("+") - concat(other: String): String { + concat(other: this): String { assert(this != null); assert(other != null); var thisLen: isize = this.length; @@ -59,7 +60,7 @@ export class String { ); } - endsWith(searchString: String, endPosition: i32 = 0x7fffffff): bool { + endsWith(searchString: this, endPosition: i32 = 0x7fffffff): bool { assert(this != null); assert(searchString != null); var end: isize = min(max(endPosition, 0), this.length); @@ -71,18 +72,18 @@ export class String { } @operator("==") - equals(other: String): bool { + private __eq(other: this): bool { assert(this != null); assert(other != null); return this.length != other.length ? false : !Heap.compare(this.ptr, other.ptr, this.length); } - includes(searchString: String, position: i32 = 0): bool { + includes(searchString: this, position: i32 = 0): bool { return this.indexOf(searchString, position) != -1; } - indexOf(searchString: String, position: i32 = 0): i32 { + indexOf(searchString: this, position: i32 = 0): i32 { assert(this != null); assert(searchString != null); var pos: isize = position; @@ -95,7 +96,7 @@ export class String { return -1; } - startsWith(searchString: String, position: i32 = 0): bool { + startsWith(searchString: this, position: i32 = 0): bool { assert(this != null); assert(searchString != null); var pos: isize = position; diff --git a/tests/compiler/showcase.optimized-inlined.wast b/tests/compiler/showcase.optimized-inlined.wast index 2cabb817..4c56074e 100644 --- a/tests/compiler/showcase.optimized-inlined.wast +++ b/tests/compiler/showcase.optimized-inlined.wast @@ -34,8 +34,8 @@ (global $showcase/AnEnum.TWO i32 (i32.const 2)) (global $showcase/AnEnum.FOUR i32 (i32.const 4)) (global $showcase/AnEnum.FIVE i32 (i32.const 5)) - (global $showcase/AnEnum.FOURTYTWO (mut i32) (i32.const 0)) - (global $showcase/AnEnum.FOURTYTHREE (mut i32) (i32.const 0)) + (global $showcase/AnEnum.FORTYTWO (mut i32) (i32.const 0)) + (global $showcase/AnEnum.FORTYTHREE (mut i32) (i32.const 0)) (global $memcpy/dest (mut i32) (i32.const 0)) (global $showcase/aClassInstance (mut i32) (i32.const 8)) (global $showcase/AClass.aStaticField (mut i32) (i32.const 0)) @@ -3944,25 +3944,25 @@ ) (unreachable) ) - (set_global $showcase/AnEnum.FOURTYTWO + (set_global $showcase/AnEnum.FORTYTWO (get_global $showcase/aMutableGlobal) ) - (set_global $showcase/AnEnum.FOURTYTHREE + (set_global $showcase/AnEnum.FORTYTHREE (i32.add - (get_global $showcase/AnEnum.FOURTYTWO) + (get_global $showcase/AnEnum.FORTYTWO) (i32.const 1) ) ) (if (i32.ne - (get_global $showcase/AnEnum.FOURTYTWO) + (get_global $showcase/AnEnum.FORTYTWO) (i32.const 42) ) (unreachable) ) (if (i32.ne - (get_global $showcase/AnEnum.FOURTYTHREE) + (get_global $showcase/AnEnum.FORTYTHREE) (i32.const 43) ) (unreachable) diff --git a/tests/compiler/showcase.optimized.wast b/tests/compiler/showcase.optimized.wast index 26b8e789..d14eddf4 100644 --- a/tests/compiler/showcase.optimized.wast +++ b/tests/compiler/showcase.optimized.wast @@ -34,8 +34,8 @@ (global $showcase/AnEnum.TWO i32 (i32.const 2)) (global $showcase/AnEnum.FOUR i32 (i32.const 4)) (global $showcase/AnEnum.FIVE i32 (i32.const 5)) - (global $showcase/AnEnum.FOURTYTWO (mut i32) (i32.const 0)) - (global $showcase/AnEnum.FOURTYTHREE (mut i32) (i32.const 0)) + (global $showcase/AnEnum.FORTYTWO (mut i32) (i32.const 0)) + (global $showcase/AnEnum.FORTYTHREE (mut i32) (i32.const 0)) (global $memcpy/dest (mut i32) (i32.const 0)) (global $showcase/aClassInstance (mut i32) (i32.const 8)) (global $showcase/AClass.aStaticField (mut i32) (i32.const 0)) @@ -3961,25 +3961,25 @@ ) (unreachable) ) - (set_global $showcase/AnEnum.FOURTYTWO + (set_global $showcase/AnEnum.FORTYTWO (get_global $showcase/aMutableGlobal) ) - (set_global $showcase/AnEnum.FOURTYTHREE + (set_global $showcase/AnEnum.FORTYTHREE (i32.add - (get_global $showcase/AnEnum.FOURTYTWO) + (get_global $showcase/AnEnum.FORTYTWO) (i32.const 1) ) ) (if (i32.ne - (get_global $showcase/AnEnum.FOURTYTWO) + (get_global $showcase/AnEnum.FORTYTWO) (i32.const 42) ) (unreachable) ) (if (i32.ne - (get_global $showcase/AnEnum.FOURTYTHREE) + (get_global $showcase/AnEnum.FORTYTHREE) (i32.const 43) ) (unreachable) diff --git a/tests/compiler/showcase.ts b/tests/compiler/showcase.ts index f2101231..24830180 100644 --- a/tests/compiler/showcase.ts +++ b/tests/compiler/showcase.ts @@ -48,15 +48,15 @@ export enum AnEnum { // 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) + FORTYTWO = aMutableGlobal, // or reference mutable values but then can't be exported + FORTYTHREE // 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); +assert(AnEnum.FORTYTWO == 42); +assert(AnEnum.FORTYTHREE == 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 @@ -94,13 +94,13 @@ class AClass { aField: i32; } class ADerivedClass extends AClass { - aNotherField: f32; - get aWildAccessorAppears(): f32 { return this.aNotherField; } - set aWildAccessorAppears(val: f32) { this.aNotherField = val; } + anotherField: f32; + get aWildAccessorAppears(): f32 { return this.anotherField; } + set aWildAccessorAppears(val: f32) { this.anotherField = val; } } var aClassInstance = changetype(8); aClassInstance.aField = 42; -aClassInstance.aNotherField = 9000; +aClassInstance.anotherField = 9000; assert(load(8) == 42); assert(load(12) == 9000); diff --git a/tests/compiler/showcase.wast b/tests/compiler/showcase.wast index 9ed8bc83..904d26b3 100644 --- a/tests/compiler/showcase.wast +++ b/tests/compiler/showcase.wast @@ -67,8 +67,8 @@ (global $showcase/AnEnum.TWO i32 (i32.const 2)) (global $showcase/AnEnum.FOUR i32 (i32.const 4)) (global $showcase/AnEnum.FIVE i32 (i32.const 5)) - (global $showcase/AnEnum.FOURTYTWO (mut i32) (i32.const 0)) - (global $showcase/AnEnum.FOURTYTHREE (mut i32) (i32.const 0)) + (global $showcase/AnEnum.FORTYTWO (mut i32) (i32.const 0)) + (global $showcase/AnEnum.FORTYTHREE (mut i32) (i32.const 0)) (global $memcpy/base i32 (i32.const 8)) (global $memcpy/dest (mut i32) (i32.const 0)) (global $showcase/aClassInstance (mut i32) (i32.const 8)) @@ -5802,12 +5802,12 @@ ) (unreachable) ) - (set_global $showcase/AnEnum.FOURTYTWO + (set_global $showcase/AnEnum.FORTYTWO (get_global $showcase/aMutableGlobal) ) - (set_global $showcase/AnEnum.FOURTYTHREE + (set_global $showcase/AnEnum.FORTYTHREE (i32.add - (get_global $showcase/AnEnum.FOURTYTWO) + (get_global $showcase/AnEnum.FORTYTWO) (i32.const 1) ) ) @@ -5850,7 +5850,7 @@ (if (i32.eqz (i32.eq - (get_global $showcase/AnEnum.FOURTYTWO) + (get_global $showcase/AnEnum.FORTYTWO) (i32.const 42) ) ) @@ -5859,7 +5859,7 @@ (if (i32.eqz (i32.eq - (get_global $showcase/AnEnum.FOURTYTHREE) + (get_global $showcase/AnEnum.FORTYTHREE) (i32.const 43) ) )