From dc74dd118d342b9ec9907029b353a405e3f23b2b Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Mon, 20 Nov 2017 23:39:50 +0100 Subject: [PATCH] Initial function calls --- src/binaryen.ts | 12 +++-- src/compiler.ts | 83 +++++++++++++++++++++-------- src/diagnosticMessages.generated.ts | 6 +++ src/diagnosticMessages.json | 3 ++ src/program.ts | 78 +++++++++++++++++++++------ 5 files changed, 141 insertions(+), 41 deletions(-) diff --git a/src/binaryen.ts b/src/binaryen.ts index ecaf5ac4..25236db9 100644 --- a/src/binaryen.ts +++ b/src/binaryen.ts @@ -440,23 +440,27 @@ export class Module { } } - createCall(target: BinaryenFunctionRef, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { + createCall(target: string, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { if (this.noEmit) return 0; + const cStr: CString = allocString(target); const cArr: CArray = allocI32Array(operands); try { - return _BinaryenCall(this.ref, target, cArr, operands.length, returnType); + return _BinaryenCall(this.ref, cStr, cArr, operands.length, returnType); } finally { _free(cArr); + _free(cStr); } } - createCallImport(target: BinaryenImportRef, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { + createCallImport(target: string, operands: BinaryenExpressionRef[], returnType: Type): BinaryenExpressionRef { if (this.noEmit) return 0; + const cStr: CString = allocString(target); const cArr: CArray = allocI32Array(operands); try { - return _BinaryenCallImport(this.ref, target, cArr, operands.length, returnType); + return _BinaryenCallImport(this.ref, cStr, cArr, operands.length, returnType); } finally { _free(cArr); + _free(cStr); } } diff --git a/src/compiler.ts b/src/compiler.ts index d9cee56c..982805f8 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,9 +1,9 @@ import { Module, MemorySegment, UnaryOp, BinaryOp, HostOp, Type as BinaryenType, Relooper } from "./binaryen"; import { PATH_DELIMITER } from "./constants"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; -import { Program, ClassPrototype, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program"; +import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program"; import { CharCode, I64, U64, normalizePath, sb } from "./util"; -import { Token } from "./tokenizer"; +import { Token, Range } from "./tokenizer"; import { Node, @@ -376,18 +376,21 @@ export class Compiler extends DiagnosticEmitter { const element: Element | null = this.program.elements.get(internalName); if (!element || element.kind != ElementKind.FUNCTION_PROTOTYPE) throw new Error("unexpected missing function"); - const resolvedTypeArguments: Type[] | null = this.program.resolveTypeArguments(declaration.typeParameters, typeArguments, contextualTypeArguments, alternativeReportNode); // reports - if (!resolvedTypeArguments) - return; - this.compileFunction(element, resolvedTypeArguments, contextualTypeArguments); + this.compileFunctionUsingTypeArguments(element, typeArguments, contextualTypeArguments, alternativeReportNode); } - compileFunction(template: FunctionPrototype, typeArguments: Type[], contextualTypeArguments: Map | null = null): void { - const instance: Function | null = template.resolve(typeArguments, contextualTypeArguments); - if (!instance || instance.compiled) + compileFunctionUsingTypeArguments(prototype: FunctionPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null) { + const instance: Function | null = prototype.resolveInclTypeArguments(typeArguments, contextualTypeArguments, alternativeReportNode); // reports + if (!instance) + return; + this.compileFunction(instance); + } + + compileFunction(instance: Function): void { + if (instance.compiled) return; - const declaration: FunctionDeclaration | null = template.declaration; + const declaration: FunctionDeclaration | null = instance.template.declaration; if (!declaration) // TODO: compile builtins throw new Error("not implemented"); @@ -470,7 +473,7 @@ export class Compiler extends DiagnosticEmitter { case ElementKind.CLASS_PROTOTYPE: if ((noTreeShaking || (element).isExport) && !(element).isGeneric) - this.compileClass(element, []); + this.compileClassUsingTypeArguments(element, []); break; case ElementKind.ENUM: @@ -479,7 +482,7 @@ export class Compiler extends DiagnosticEmitter { case ElementKind.FUNCTION_PROTOTYPE: if ((noTreeShaking || (element).isExport) && !(element).isGeneric) - this.compileFunction(element, []); + this.compileFunctionUsingTypeArguments(element, []); break; case ElementKind.GLOBAL: @@ -508,7 +511,7 @@ export class Compiler extends DiagnosticEmitter { case ElementKind.CLASS_PROTOTYPE: if (!(element).isGeneric) - this.compileClass(element, []); + this.compileClassUsingTypeArguments(element, []); break; case ElementKind.ENUM: @@ -517,7 +520,7 @@ export class Compiler extends DiagnosticEmitter { case ElementKind.FUNCTION_PROTOTYPE: if (!(element).isGeneric) - this.compileFunction(element, []); + this.compileFunctionUsingTypeArguments(element, []); break; case ElementKind.GLOBAL: @@ -538,13 +541,17 @@ export class Compiler extends DiagnosticEmitter { const element: Element | null = this.program.elements.get(internalName); if (!element || element.kind != ElementKind.CLASS_PROTOTYPE) throw new Error("unexpected missing class"); - const resolvedTypeArguments: Type[] | null = this.program.resolveTypeArguments(declaration.typeParameters, typeArguments, contextualTypeArguments, alternativeReportNode); // reports - if (!resolvedTypeArguments) - return; - this.compileClass(element, resolvedTypeArguments, contextualTypeArguments); + this.compileClassUsingTypeArguments(element, typeArguments, contextualTypeArguments, alternativeReportNode); } - compileClass(cls: ClassPrototype, typeArguments: Type[], contextualTypeArguments: Map | null = null) { + compileClassUsingTypeArguments(prototype: ClassPrototype, typeArguments: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): void { + const instance: Class | null = prototype.resolveInclTypeArguments(typeArguments, contextualTypeArguments, alternativeReportNode); + if (!instance) + return; + this.compileClass(instance); + } + + compileClass(cls: Class) { throw new Error("not implemented"); } @@ -1319,14 +1326,46 @@ export class Compiler extends DiagnosticEmitter { const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports if (!element) return this.module.createUnreachable(); - if (element.kind != ElementKind.FUNCTION_PROTOTYPE) { - // TODO: report 'Cannot invoke an expression whose type lacks a call signature.' + if (element.kind == ElementKind.FUNCTION_PROTOTYPE) { + const functionInstance: Function | null = (element).resolveInclTypeArguments(expression.typeArguments, this.currentFunction.contextualTypeArguments, expression); // reports + if (!functionInstance) + return this.module.createUnreachable(); + return this.compileCall(functionInstance, expression.arguments, expression); + } + this.error(DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, expression.range, element.internalName); + return this.module.createUnreachable(); + } + + compileCall(functionInstance: Function, argumentExpressions: Expression[], reportNode: Node): BinaryenExpressionRef { + if (!functionInstance.compiled) + this.compileFunction(functionInstance); + const parameters: Parameter[] = functionInstance.parameters; + let k: i32 = parameters.length; + if (argumentExpressions.length > k) { + this.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, k.toString(10), argumentExpressions.length.toString(10)); return this.module.createUnreachable(); } - throw new Error("not implemented"); + const operands: BinaryenExpressionRef[] = new Array(k); + for (let i: i32 = 0; i < k; ++i) { + if (argumentExpressions.length > i) { + operands[i] = this.compileExpression(argumentExpressions[i], parameters[i].type); + } else { + const initializer: Expression | null = parameters[i].initializer; + if (initializer) { + operands[i] = this.compileExpression(initializer, parameters[i].type); + } else { + this.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range, (i + 1).toString(10), argumentExpressions.length.toString(10)); + return this.module.createUnreachable(); + } + } + } + return this.module.createCall(functionInstance.internalName, operands, typeToBinaryenType(functionInstance.returnType)); } compileElementAccessExpression(expression: ElementAccessExpression, contextualType: Type): BinaryenExpressionRef { + const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports + if (!element) + return this.module.createUnreachable(); throw new Error("not implemented"); } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 1c870a6f..42c7af2c 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -52,12 +52,15 @@ export enum DiagnosticCode { Type_0_is_not_assignable_to_type_1 = 2322, _this_cannot_be_referenced_in_current_location = 2332, Property_0_does_not_exist_on_type_1 = 2339, + Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures = 2349, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access = 2364, Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391, Duplicate_function_implementation = 2393, Export_declaration_conflicts_with_exported_declaration_of_0 = 2484, The_target_of_an_assignment_must_be_a_variable_or_a_property_access = 2541, + Expected_0_arguments_but_got_1 = 2554, + Expected_at_least_0_arguments_but_got_1 = 2555, Expected_0_type_arguments_but_got_1 = 2558, File_0_not_found = 6054 } @@ -115,12 +118,15 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2322: return "Type '{0}' is not assignable to type '{1}'."; case 2332: return "'this' cannot be referenced in current location."; case 2339: return "Property '{0}' does not exist on type '{1}'."; + case 2349: return "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures."; case 2357: return "The operand of an increment or decrement operator must be a variable or a property access."; case 2364: return "The left-hand side of an assignment expression must be a variable or a property access."; case 2391: return "Function implementation is missing or not immediately following the declaration."; case 2393: return "Duplicate function implementation."; case 2484: return "Export declaration conflicts with exported declaration of '{0}'."; case 2541: return "The target of an assignment must be a variable or a property access."; + 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}."; case 6054: return "File '{0}' not found."; default: return ""; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 76f9c690..3c9bc79f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -52,12 +52,15 @@ "Type '{0}' is not assignable to type '{1}'.": 2322, "'this' cannot be referenced in current location.": 2332, "Property '{0}' does not exist on type '{1}'.": 2339, + "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": 2349, "The operand of an increment or decrement operator must be a variable or a property access.": 2357, "The left-hand side of an assignment expression must be a variable or a property access.": 2364, "Function implementation is missing or not immediately following the declaration.": 2391, "Duplicate function implementation.": 2393, "Export declaration conflicts with exported declaration of '{0}'.": 2484, "The target of an assignment must be a variable or a property access.": 2541, + "Expected {0} arguments, but got {1}.": 2554, + "Expected at least {0} arguments, but got {1}.": 2555, "Expected {0} type arguments, but got {1}.": 2558, "File '{0}' not found.": 6054 diff --git a/src/program.ts b/src/program.ts index ad5cf91d..c93e8cf7 100644 --- a/src/program.ts +++ b/src/program.ts @@ -508,19 +508,19 @@ export class Program extends DiagnosticEmitter { return null; } - resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[], contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Type[] | null { + resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Type[] | null { const parameterCount: i32 = typeParameters.length; - const argumentCount: i32 = typeArgumentNodes.length; + const argumentCount: i32 = typeArgumentNodes ? typeArgumentNodes.length : 0; if (parameterCount != argumentCount) { if (argumentCount) - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, Range.join(typeArgumentNodes[0].range, typeArgumentNodes[argumentCount - 1].range), parameterCount.toString(10), argumentCount.toString(10)); + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, Range.join((typeArgumentNodes)[0].range, (typeArgumentNodes)[argumentCount - 1].range), parameterCount.toString(10), argumentCount.toString(10)); else if (alternativeReportNode) this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, alternativeReportNode.range.atEnd, parameterCount.toString(10), "0"); return null; } const typeArguments: Type[] = new Array(parameterCount); for (let i: i32 = 0; i < parameterCount; ++i) { - const type: Type | null = this.resolveType(typeArgumentNodes[i], contextualTypeArguments, true); // reports + const type: Type | null = this.resolveType((typeArgumentNodes)[i], contextualTypeArguments, true); // reports if (!type) return null; // TODO: check extendsType @@ -531,7 +531,7 @@ export class Program extends DiagnosticEmitter { resolveElement(expression: Expression, contextualFunction: Function): Element | null { - // this + // this -> Class if (expression.kind == NodeKind.THIS) { if (contextualFunction.instanceMethodOf) return contextualFunction.instanceMethodOf; @@ -539,6 +539,8 @@ export class Program extends DiagnosticEmitter { return null; } + let ret: Element; + // local or global name if (expression.kind == NodeKind.IDENTIFIER) { const name: string = (expression).name; @@ -557,10 +559,10 @@ export class Program extends DiagnosticEmitter { // static or instance property (incl. enum values) or method } else if (expression.kind == NodeKind.PROPERTYACCESS) { const target: Element | null = this.resolveElement((expression).expression, contextualFunction); // reports - let member: Element | null = null; if (!target) return null; const propertyName: string = (expression).property.name; + let member: Element | null = null; if (target.kind == ElementKind.ENUM) member = (target).members.get(propertyName); else if (target.kind == ElementKind.CLASS_PROTOTYPE) @@ -702,10 +704,12 @@ export class Parameter { name: string; type: Type; + initializer: Expression | null; - constructor(name: string, type: Type) { + constructor(name: string, type: Type, initializer: Expression | null = null) { this.name = name; this.type = type; + this.initializer = initializer; } } @@ -802,26 +806,51 @@ export class FunctionPrototype extends Element { this.instances.set(instanceKey, instance); return instance; } + + resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null, alternativeReportNode: Node | null): Function | null { + let resolvedTypeArguments: Type[] | null; + if (this.isGeneric) { + if (!this.declaration) + throw new Error("not implemented"); // generic builtin + resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode); + if (!resolvedTypeArguments) + return null; + } else { + // TODO: check typeArgumentNodes being empty + resolvedTypeArguments = []; + } + return this.resolve(resolvedTypeArguments, contextualTypeArguments); + } } /** A resolved function. */ export class Function extends Element { kind = ElementKind.FUNCTION; - template: FunctionPrototype; - typeArguments: Type[]; - parameters: Parameter[]; - returnType: Type; - instanceMethodOf: Class | null; - locals: Map = new Map(); - additionalLocals: Type[] = []; // without parameters - breakContext: string | null = null; + /** Underlying function template. */ + template: FunctionPrototype; + /** Concrete type arguments. */ + typeArguments: Type[]; + /** Concrete function parameters. */ + parameters: Parameter[]; + /** Concrete return type. */ + returnType: Type; + /** If a method, the concrete class it is a member of. */ + instanceMethodOf: Class | null; + /** Map of locals by name. */ + locals: Map = new Map(); + /** List of additional non-parameter locals. */ + additionalLocals: Type[] = []; + /** Current break context label. */ + breakContext: string | null = null; + /** Contextual type arguments. */ contextualTypeArguments: Map = new Map(); private breakMajor: i32 = 0; private breakMinor: i32 = 0; + /** Constructs a new concrete function. */ constructor(template: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) { super(template.program, internalName); this.template = template; @@ -841,8 +870,10 @@ export class Function extends Element { } } + /** Tests if this function is an instance method. */ get isInstance(): bool { return this.instanceMethodOf != null; } + /** Adds a local of the specified type, with an optional name. */ addLocal(type: Type, name: string | null = null): Local { // if it has a name, check previously as this method will throw otherwise let localIndex = this.parameters.length + this.additionalLocals.length; @@ -857,12 +888,14 @@ export class Function extends Element { return local; } + /** Enters a(nother) break context. */ enterBreakContext(): string { if (!this.breakMinor) this.breakMajor++; return this.breakContext = this.breakMajor.toString(10) + "." + (++this.breakMinor).toString(10); } + /** Leaves the current break context. */ leaveBreakContext(): void { if (--this.breakMinor < 0) throw new Error("unexpected unbalanced break context"); @@ -931,6 +964,21 @@ export class ClassPrototype extends Namespace { throw new Error("unexpected instantiation of internal class"); throw new Error("not implemented"); } + + resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null, alternativeReportNode: Node | null): Class | null { + let resolvedTypeArguments: Type[] | null; + if (this.isGeneric) { + if (!this.declaration) + throw new Error("not implemented"); // generic builtin + resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode); + if (!resolvedTypeArguments) + return null; + } else { + // TODO: check typeArgumentNodes being empty + resolvedTypeArguments = []; + } + return this.resolve(resolvedTypeArguments, contextualTypeArguments); + } } /** A resolved class. */