mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-19 09:51:33 +00:00
Initial element access compilation; Carefully approaching std array
This commit is contained in:
12
src/ast.ts
12
src/ast.ts
@ -363,12 +363,12 @@ export abstract class Node {
|
||||
stmt.decoratorKind = DecoratorKind.OPERATOR;
|
||||
break;
|
||||
|
||||
case "struct":
|
||||
stmt.decoratorKind = DecoratorKind.STRUCT;
|
||||
case "explicit":
|
||||
stmt.decoratorKind = DecoratorKind.EXPLICIT;
|
||||
break;
|
||||
|
||||
case "size":
|
||||
stmt.decoratorKind = DecoratorKind.SIZE;
|
||||
case "offset":
|
||||
stmt.decoratorKind = DecoratorKind.OFFSET;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1382,8 +1382,8 @@ export const enum DecoratorKind {
|
||||
CUSTOM,
|
||||
GLOBAL,
|
||||
OPERATOR,
|
||||
STRUCT,
|
||||
SIZE
|
||||
EXPLICIT,
|
||||
OFFSET
|
||||
}
|
||||
|
||||
/** Depresents a decorator. */
|
||||
|
@ -1349,7 +1349,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
|
||||
}
|
||||
arg0 = compiler.compileExpression(operands[0], usizeType);
|
||||
compiler.currentType = typeArguments[0];
|
||||
return module.createLoad(typeArguments[0].size >>> 3, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0, typeArguments[0].toNativeType());
|
||||
return module.createLoad(typeArguments[0].byteSize, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0, typeArguments[0].toNativeType());
|
||||
|
||||
case "store": // store<T?>(offset: usize, value: T) -> void
|
||||
compiler.currentType = Type.void;
|
||||
@ -1372,7 +1372,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
|
||||
}
|
||||
type = compiler.currentType;
|
||||
compiler.currentType = Type.void;
|
||||
return module.createStore(type.size >>> 3, arg0, arg1, type.toNativeType());
|
||||
return module.createStore(type.byteSize, arg0, arg1, type.toNativeType());
|
||||
|
||||
case "sizeof": // sizeof<T!>() -> usize
|
||||
compiler.currentType = usizeType;
|
||||
|
@ -2217,6 +2217,16 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Property>element).internalName);
|
||||
return this.module.createUnreachable();
|
||||
|
||||
case ElementKind.FUNCTION_PROTOTYPE:
|
||||
if (expression.kind == NodeKind.ELEMENTACCESS) { // @operator("[]")
|
||||
assert(resolved.target && resolved.target.kind == ElementKind.CLASS && element.simpleName == (<Class>resolved.target).prototype.fnIndexedGet)
|
||||
var resolvedIndexedSet = (<FunctionPrototype>element).resolve(null);
|
||||
if (resolvedIndexedSet) {
|
||||
elementType = resolvedIndexedSet.returnType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fall-through
|
||||
default:
|
||||
this.error(DiagnosticCode.Operation_not_supported, expression.range);
|
||||
return this.module.createUnreachable();
|
||||
@ -2275,11 +2285,11 @@ export class Compiler extends DiagnosticEmitter {
|
||||
this.currentType = tee ? (<Field>element).type : Type.void;
|
||||
var elementNativeType = (<Field>element).type.toNativeType();
|
||||
if (!tee)
|
||||
return this.module.createStore((<Field>element).type.byteSize, targetExpr, valueWithCorrectType, elementNativeType, (<Field>element).memoryOffset);
|
||||
return this.module.createStore((<Field>element).type.size >> 3, targetExpr, valueWithCorrectType, elementNativeType, (<Field>element).memoryOffset);
|
||||
tempLocal = this.currentFunction.getAndFreeTempLocal((<Field>element).type);
|
||||
return this.module.createBlock(null, [ // TODO: simplify if valueWithCorrectType has no side effects
|
||||
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.size >> 3, targetExpr, this.module.createGetLocal(tempLocal.index, elementNativeType), elementNativeType, (<Field>element).memoryOffset),
|
||||
this.module.createGetLocal(tempLocal.index, elementNativeType)
|
||||
], elementNativeType);
|
||||
|
||||
@ -2325,6 +2335,38 @@ export class Compiler extends DiagnosticEmitter {
|
||||
} else
|
||||
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Property>element).internalName);
|
||||
return this.module.createUnreachable();
|
||||
|
||||
case ElementKind.FUNCTION_PROTOTYPE:
|
||||
if (expression.kind == NodeKind.ELEMENTACCESS) { // @operator("[]")
|
||||
assert(resolved.target && resolved.target.kind == ElementKind.CLASS);
|
||||
var resolvedIndexedGet = (<FunctionPrototype>element).resolve();
|
||||
if (!resolvedIndexedGet)
|
||||
return this.module.createUnreachable();
|
||||
var indexedSetName = (<Class>resolved.target).prototype.fnIndexedSet;
|
||||
var indexedSet: Element | null;
|
||||
if (indexedSetName != null && (<Class>resolved.target).members && (indexedSet = (<Map<string,Element>>(<Class>resolved.target).members).get(indexedSetName)) && indexedSet.kind == ElementKind.FUNCTION_PROTOTYPE) { // @operator("[]=")
|
||||
var resolvedIndexedSet = (<FunctionPrototype>indexedSet).resolve();
|
||||
if (!resolvedIndexedSet)
|
||||
return this.module.createUnreachable();
|
||||
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32, ConversionKind.NONE);
|
||||
assert(this.currentType.classType);
|
||||
var elementExpr = this.compileExpression((<ElementAccessExpression>expression).elementExpression, Type.i32);
|
||||
if (!tee) {
|
||||
this.currentType = resolvedIndexedSet.returnType;
|
||||
return this.makeCall(resolvedIndexedSet, [ targetExpr, elementExpr, valueWithCorrectType ]);
|
||||
}
|
||||
this.currentType = resolvedIndexedGet.returnType;
|
||||
tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType);
|
||||
return this.module.createBlock(null, [
|
||||
this.makeCall(resolvedIndexedSet, [ targetExpr, elementExpr, this.module.createTeeLocal(tempLocal.index, valueWithCorrectType) ]),
|
||||
this.module.createGetLocal(tempLocal.index, tempLocal.type.toNativeType()) // TODO: could be different from an actual __get (needs 2 temp locals)
|
||||
], this.currentType.toNativeType());
|
||||
} else {
|
||||
this.error(DiagnosticCode.Index_signature_in_type_0_only_permits_reading, expression.range, (<Class>resolved.target).internalName);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
}
|
||||
// fall-through
|
||||
}
|
||||
this.error(DiagnosticCode.Operation_not_supported, expression.range);
|
||||
return this.module.createUnreachable();
|
||||
@ -2465,8 +2507,11 @@ export class Compiler extends DiagnosticEmitter {
|
||||
var resolved = this.program.resolveElementAccess(expression, this.currentFunction); // reports
|
||||
if (!resolved)
|
||||
return this.module.createUnreachable();
|
||||
|
||||
throw new Error("not implemented"); // TODO
|
||||
assert(resolved.element.kind == ElementKind.FUNCTION_PROTOTYPE && resolved.target && resolved.target.kind == ElementKind.CLASS);
|
||||
var instance = (<FunctionPrototype>resolved.element).resolve(null, (<Class>resolved.target).contextualTypeArguments);
|
||||
if (!instance)
|
||||
return this.module.createUnreachable();
|
||||
return this.compileCall(instance, [ expression.expression, expression.elementExpression ], expression);
|
||||
}
|
||||
|
||||
compileIdentifierExpression(expression: IdentifierExpression, contextualType: Type): ExpressionRef {
|
||||
@ -2632,7 +2677,7 @@ export class Compiler extends DiagnosticEmitter {
|
||||
assert((<Field>element).memoryOffset >= 0);
|
||||
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32);
|
||||
this.currentType = (<Field>element).type;
|
||||
return this.module.createLoad((<Field>element).type.byteSize, (<Field>element).type.is(TypeFlags.SIGNED | TypeFlags.INTEGER),
|
||||
return this.module.createLoad((<Field>element).type.size >> 3, (<Field>element).type.is(TypeFlags.SIGNED | TypeFlags.INTEGER),
|
||||
targetExpr,
|
||||
(<Field>element).type.toNativeType(),
|
||||
(<Field>element).memoryOffset
|
||||
|
@ -1,8 +1,14 @@
|
||||
// internal naming scheme
|
||||
|
||||
/** Path delimited inserted between file system levels. */
|
||||
export const PATH_DELIMITER = "/";
|
||||
/** Substitution used to indicate the parent directory. */
|
||||
export const PARENT_SUBST = "..";
|
||||
/** Function name prefix used for getters. */
|
||||
export const GETTER_PREFIX = "get:";
|
||||
/** Function name prefix used for setters. */
|
||||
export const SETTER_PREFIX = "set:";
|
||||
/** Delimiter used between class names and instance members. */
|
||||
export const INSTANCE_DELIMITER = "#";
|
||||
/** Delimited used between class and namespace names and static members. */
|
||||
export const STATIC_DELIMITER = ".";
|
||||
|
@ -79,6 +79,7 @@ export enum DiagnosticCode {
|
||||
Export_declaration_conflicts_with_exported_declaration_of_0 = 2484,
|
||||
Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property = 2540,
|
||||
The_target_of_an_assignment_must_be_a_variable_or_a_property_access = 2541,
|
||||
Index_signature_in_type_0_only_permits_reading = 2542,
|
||||
Expected_0_arguments_but_got_1 = 2554,
|
||||
Expected_at_least_0_arguments_but_got_1 = 2555,
|
||||
Expected_0_type_arguments_but_got_1 = 2558,
|
||||
@ -166,6 +167,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
|
||||
case 2484: return "Export declaration conflicts with exported declaration of '{0}'.";
|
||||
case 2540: return "Cannot assign to '{0}' because it is a constant or a read-only property.";
|
||||
case 2541: return "The target of an assignment must be a variable or a property access.";
|
||||
case 2542: return "Index signature in type '{0}' only permits reading.";
|
||||
case 2554: return "Expected {0} arguments, but got {1}.";
|
||||
case 2555: return "Expected at least {0} arguments, but got {1}.";
|
||||
case 2558: return "Expected {0} type arguments, but got {1}.";
|
||||
|
@ -79,6 +79,7 @@
|
||||
"Export declaration conflicts with exported declaration of '{0}'.": 2484,
|
||||
"Cannot assign to '{0}' because it is a constant or a read-only property.": 2540,
|
||||
"The target of an assignment must be a variable or a property access.": 2541,
|
||||
"Index signature in type '{0}' only permits reading.": 2542,
|
||||
"Expected {0} arguments, but got {1}.": 2554,
|
||||
"Expected at least {0} arguments, but got {1}.": 2555,
|
||||
"Expected {0} type arguments, but got {1}.": 2558,
|
||||
|
@ -1580,6 +1580,19 @@ export class Parser extends DiagnosticEmitter {
|
||||
|
||||
var startPos = expr.range.start;
|
||||
|
||||
// ElementAccessExpression
|
||||
if (tn.skip(Token.OPENBRACKET)) {
|
||||
next = this.parseExpression(tn); // resets precedence
|
||||
if (!next)
|
||||
return null;
|
||||
if (tn.skip(Token.CLOSEBRACKET))
|
||||
expr = Node.createElementAccessExpression(<Expression>expr, <Expression>next, tn.range(startPos, tn.pos));
|
||||
else {
|
||||
this.error(DiagnosticCode._0_expected, tn.range(), "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// CallExpression
|
||||
var typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn); // skips '(' on success
|
||||
// there might be better ways to distinguish a LESSTHAN from a CALL with type arguments
|
||||
@ -1593,7 +1606,6 @@ export class Parser extends DiagnosticEmitter {
|
||||
var token: Token;
|
||||
var next: Expression | null = null;
|
||||
var nextPrecedence: Precedence;
|
||||
|
||||
while ((nextPrecedence = determinePrecedence(token = tn.peek())) >= precedence) { // precedence climbing
|
||||
tn.next();
|
||||
|
||||
@ -1604,19 +1616,6 @@ export class Parser extends DiagnosticEmitter {
|
||||
return null;
|
||||
expr = Node.createAssertionExpression(AssertionKind.AS, expr, toType, tn.range(startPos, tn.pos));
|
||||
|
||||
// ElementAccessExpression
|
||||
} else if (token == Token.OPENBRACKET) {
|
||||
next = this.parseExpression(tn); // resets precedence
|
||||
if (!next)
|
||||
return null;
|
||||
|
||||
if (tn.skip(Token.CLOSEBRACKET))
|
||||
expr = Node.createElementAccessExpression(<Expression>expr, <Expression>next, tn.range(startPos, tn.pos));
|
||||
else {
|
||||
this.error(DiagnosticCode._0_expected, tn.range(), "]");
|
||||
return null;
|
||||
}
|
||||
|
||||
// UnaryPostfixExpression
|
||||
} else if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) {
|
||||
if (expr.kind != NodeKind.IDENTIFIER && expr.kind != NodeKind.ELEMENTACCESS && expr.kind != NodeKind.PROPERTYACCESS)
|
||||
|
@ -290,8 +290,8 @@ export class Program extends DiagnosticEmitter {
|
||||
|
||||
this.checkGlobalAlias(prototype, declaration);
|
||||
|
||||
if (hasDecorator("struct", declaration.decorators)) {
|
||||
prototype.isStruct = true;
|
||||
if (hasDecorator("explicit", declaration.decorators)) {
|
||||
prototype.isExplicit = true;
|
||||
if (declaration.implementsTypes && declaration.implementsTypes.length)
|
||||
this.error(DiagnosticCode.Structs_cannot_implement_interfaces, Range.join(declaration.name.range, declaration.implementsTypes[declaration.implementsTypes.length - 1].range));
|
||||
} else if (declaration.implementsTypes.length)
|
||||
@ -413,8 +413,8 @@ export class Program extends DiagnosticEmitter {
|
||||
} else
|
||||
classPrototype.instanceMembers = new Map();
|
||||
instancePrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype);
|
||||
// if (classPrototype.isStruct && instancePrototype.isAbstract) {
|
||||
// this.error( Structs cannot declare abstract methods. );
|
||||
// if (classPrototype.isExplicit && instancePrototype.isAbstract) {
|
||||
// this.error( Explicit classes cannot declare abstract methods. );
|
||||
// }
|
||||
classPrototype.instanceMembers.set(name, instancePrototype);
|
||||
}
|
||||
@ -436,19 +436,19 @@ export class Program extends DiagnosticEmitter {
|
||||
switch ((<StringLiteralExpression>firstArg).value) {
|
||||
|
||||
case "[]":
|
||||
classPrototype.opIndexedGet = instancePrototype;
|
||||
classPrototype.fnIndexedGet = instancePrototype.simpleName;
|
||||
break;
|
||||
|
||||
case "[]=":
|
||||
classPrototype.opIndexedSet = instancePrototype;
|
||||
classPrototype.fnIndexedSet = instancePrototype.simpleName;
|
||||
break;
|
||||
|
||||
case "+":
|
||||
classPrototype.opConcat = instancePrototype;
|
||||
classPrototype.fnConcat = instancePrototype.simpleName;
|
||||
break;
|
||||
|
||||
case "==":
|
||||
classPrototype.opEquals = instancePrototype;
|
||||
classPrototype.fnEquals = instancePrototype.simpleName;
|
||||
break;
|
||||
|
||||
default: // TBD: does it make sense to provide more, even though not JS/TS-compatible?
|
||||
@ -1078,13 +1078,15 @@ export class Program extends DiagnosticEmitter {
|
||||
var target = resolvedElement.element;
|
||||
switch (target.kind) {
|
||||
|
||||
// TBD: should indexed access on static classes, like `Heap`, be a supported as well?
|
||||
case ElementKind.CLASS:
|
||||
var type = (<Class>target).type;
|
||||
case ElementKind.GLOBAL:
|
||||
case ElementKind.LOCAL:
|
||||
case ElementKind.FIELD:
|
||||
var type = (<VariableLikeElement>target).type;
|
||||
if (type.classType) {
|
||||
var indexedGet: FunctionPrototype | null;
|
||||
if (indexedGet = (target = type.classType).prototype.opIndexedGet)
|
||||
return resolvedElement.set(indexedGet).withTarget(target, targetExpression);
|
||||
var indexedGetName = (target = type.classType).prototype.fnIndexedGet;
|
||||
var indexedGet: Element | null;
|
||||
if (indexedGetName != null && target.members && (indexedGet = target.members.get(indexedGetName)) && indexedGet.kind == ElementKind.FUNCTION_PROTOTYPE)
|
||||
return resolvedElement.set(indexedGet).withTarget(type.classType, targetExpression);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1221,8 +1223,8 @@ export enum ElementFlags {
|
||||
PRIVATE = 1 << 15,
|
||||
/** Is an abstract member. */
|
||||
ABSTRACT = 1 << 16,
|
||||
/** Is a struct-like class with limited capabilites. */
|
||||
STRUCT = 1 << 17,
|
||||
/** Is an explicitly layed out and allocated class with limited capabilites. */
|
||||
EXPLICIT = 1 << 17,
|
||||
/** Has already inherited base class static members. */
|
||||
HAS_STATIC_BASE_MEMBERS = 1 << 18
|
||||
}
|
||||
@ -1877,13 +1879,13 @@ export class ClassPrototype extends Element {
|
||||
basePrototype: ClassPrototype | null = null; // set in Program#initialize
|
||||
|
||||
/** Overloaded indexed get method, if any. */
|
||||
opIndexedGet: FunctionPrototype | null = null; // TODO: indexedGet and indexedSet as an accessor?
|
||||
fnIndexedGet: string | null = null;
|
||||
/** Overloaded indexed set method, if any. */
|
||||
opIndexedSet: FunctionPrototype | null = null;
|
||||
fnIndexedSet: string | null = null;
|
||||
/** Overloaded concatenation method, if any. */
|
||||
opConcat: FunctionPrototype | null = null;
|
||||
fnConcat: string | null = null;
|
||||
/** Overloaded equality comparison method, if any. */
|
||||
opEquals: FunctionPrototype | null = null;
|
||||
fnEquals: string | null = null;
|
||||
|
||||
constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) {
|
||||
super(program, simpleName, internalName);
|
||||
@ -1903,9 +1905,9 @@ export class ClassPrototype extends Element {
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether a struct-like class with limited capabilities or not. */
|
||||
get isStruct(): bool { return (this.flags & ElementFlags.STRUCT) != 0; }
|
||||
set isStruct(is: bool) { if (is) this.flags |= ElementFlags.STRUCT; else this.flags &= ~ElementFlags.STRUCT; }
|
||||
/** Whether explicitly layed out and allocated */
|
||||
get isExplicit(): bool { return (this.flags & ElementFlags.EXPLICIT) != 0; }
|
||||
set isExplicit(is: bool) { if (is) this.flags |= ElementFlags.EXPLICIT; else this.flags &= ~ElementFlags.EXPLICIT; }
|
||||
|
||||
resolve(typeArguments: Type[] | null, contextualTypeArguments: Map<string,Type> | null = null): Class | null {
|
||||
var instanceKey = typeArguments ? typesToString(typeArguments) : "";
|
||||
@ -1933,7 +1935,7 @@ export class ClassPrototype extends Element {
|
||||
this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
|
||||
return null;
|
||||
}
|
||||
if (baseClass.prototype.isStruct != this.isStruct) {
|
||||
if (baseClass.prototype.isExplicit != this.isExplicit) {
|
||||
this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
|
||||
return null;
|
||||
}
|
||||
@ -1957,9 +1959,9 @@ export class ClassPrototype extends Element {
|
||||
instance.contextualTypeArguments = contextualTypeArguments;
|
||||
this.instances.set(instanceKey, instance);
|
||||
|
||||
var memoryOffset: i32 = 0;
|
||||
var memoryOffset: u32 = 0;
|
||||
if (baseClass) {
|
||||
memoryOffset = baseClass.type.byteSize;
|
||||
memoryOffset = baseClass.currentMemoryOffset;
|
||||
if (baseClass.members) {
|
||||
if (!instance.members)
|
||||
instance.members = new Map();
|
||||
@ -1983,7 +1985,7 @@ export class ClassPrototype extends Element {
|
||||
var fieldType = this.program.resolveType(fieldDeclaration.type, instance.contextualTypeArguments); // reports
|
||||
if (fieldType) {
|
||||
var fieldInstance = new Field(<FieldPrototype>member, (<FieldPrototype>member).internalName, fieldType);
|
||||
switch (fieldType.size >> 3) { // align (byteSize might vary if a class type)
|
||||
switch (fieldType.byteSize) { // align
|
||||
case 1: break;
|
||||
case 2: if (memoryOffset & 1) ++memoryOffset; break;
|
||||
case 4: if (memoryOffset & 3) memoryOffset = (memoryOffset | 3) + 1; break;
|
||||
@ -2015,7 +2017,7 @@ export class ClassPrototype extends Element {
|
||||
}
|
||||
}
|
||||
|
||||
instance.type.byteSize = memoryOffset; // sizeof<this>() is its byte size in memory
|
||||
instance.currentMemoryOffset = memoryOffset; // sizeof<this>() is its byte size in memory
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -2051,6 +2053,8 @@ export class Class extends Element {
|
||||
base: Class | null;
|
||||
/** Contextual type arguments for fields and methods. */
|
||||
contextualTypeArguments: Map<string,Type> | null = null;
|
||||
/** Current member memory offset. */
|
||||
currentMemoryOffset: u32 = 0;
|
||||
|
||||
/** Constructs a new class. */
|
||||
constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] | null = null, base: Class | null = null) {
|
||||
|
@ -76,8 +76,8 @@ export class Type {
|
||||
/** Type flags. */
|
||||
flags: TypeFlags;
|
||||
/** Size in bits. */
|
||||
size: i32;
|
||||
/** Size in bytes. */
|
||||
size: u32;
|
||||
/** Size in bytes. Ceiled to 8-bits. */
|
||||
byteSize: i32;
|
||||
/** Underlying class type, if a class type. */
|
||||
classType: Class | null;
|
||||
|
Reference in New Issue
Block a user