From 96f07d04c3461074f6e1261acd85e08836706b14 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Mon, 23 Oct 2023 13:33:57 +0200 Subject: [PATCH 1/8] feat: implementation of PythonCall annotation --- src/cli/generator.ts | 44 ++++++++++++++++++- src/language/builtins/safe-ds-annotations.ts | 14 ++++++ .../safeds/lang/codeGeneration.sdsstub | 10 +++++ .../generation/expressions/call/input.sdstest | 12 +++++ .../output/tests/generator/call/gen_input.py | 3 ++ .../expressions/member access/input.sdstest | 3 ++ .../tests/generator/memberAccess/gen_input.py | 1 + 7 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/cli/generator.ts b/src/cli/generator.ts index 59fe7b7ba..4399d295a 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -18,6 +18,7 @@ import { isSdsEnumVariant, isSdsExpressionLambda, isSdsExpressionStatement, + isSdsFunction, isSdsIndexedAccess, isSdsInfixOperation, isSdsList, @@ -59,12 +60,12 @@ import { NodeFileSystem } from 'langium/node'; import { getAbstractResults, getAssignees, - streamBlockLambdaResults, getImportedDeclarations, getImports, - isRequiredParameter, getModuleMembers, getStatements, + isRequiredParameter, + streamBlockLambdaResults, } from '../language/helpers/nodeProperties.js'; import { IdManager } from '../language/helpers/idManager.js'; import { isInStubFile } from '../language/helpers/fileExtensions.js'; @@ -381,6 +382,20 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio return frame.getUniqueLambdaBlockName(expression); } if (isSdsCall(expression)) { + if (isSdsReference(expression.receiver) && isSdsFunction(expression.receiver.target.ref)) { + const pythonCall = frame.getServices().builtins.Annotations.getPythonCall(expression.receiver.target.ref); + if (pythonCall) { + const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); + return generatePythonCall(pythonCall, argumentsMap); + } + } + if (isSdsMemberAccess(expression.receiver) && isSdsReference(expression.receiver.member) && isSdsFunction(expression.receiver.member.target.ref)) { + const pythonCall = frame.getServices().builtins.Annotations.getPythonCall(expression.receiver.member.target.ref); + if (pythonCall) { + const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); + return generatePythonCall(pythonCall, argumentsMap, generateExpression(expression.receiver.receiver, frame)); + } + } const sortedArgs = sortArguments(frame.getServices(), expression.argumentList.arguments); return expandToString`${generateExpression(expression.receiver, frame)}(${sortedArgs .map((arg) => generateArgument(arg, frame)) @@ -467,6 +482,31 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio throw new Error(`Unknown expression type: ${expression.$type}`); }; +const generatePythonCall = function ( + pythonCall: string, + argumentsMap: Map, + thisParam: string | undefined = undefined, +): string { + if (thisParam) { + argumentsMap.set('this', thisParam); + } + // Extract each placeholder from annotation: Match only strings that start with '$' + // Use look-ahead to only match up to a '.', ',', ')' or a whitespace + return pythonCall.replace(/\$.+?(?=[\s.,)])/gu, (value) => argumentsMap.get(value.substring(1))!); +}; + +const getArgumentsMap = function (argumentList: SdsArgument[], frame: GenerationInfoFrame): Map { + const argumentsMap = new Map(); + argumentList.reduce((map, value) => { + map.set( + frame.getServices().helpers.NodeMapper.argumentToParameter(value)?.name!, + generateArgument(value, frame), + ); + return map; + }, argumentsMap); + return argumentsMap; +}; + const sortArguments = function (services: SafeDsServices, argumentList: SdsArgument[]): SdsArgument[] { // $containerIndex contains the index of the parameter in the receivers parameter list const parameters = argumentList.map((argument) => { diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index 74212c980..56028c464 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -4,6 +4,7 @@ import { SdsAnnotatedObject, SdsAnnotation, SdsEnumVariant, + SdsFunction, SdsModule, SdsParameter, } from '../generated/ast.js'; @@ -91,6 +92,19 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { return this.getAnnotation(CODE_GENERATION_URI, 'PythonName'); } + getPythonCall(node: SdsFunction | undefined): string | undefined { + const value = this.getArgumentValue(node, this.PythonCall, 'callSpecification'); + if (value instanceof StringConstant) { + return value.value; + } else { + return undefined; + } + } + + get PythonCall(): SdsAnnotation | undefined { + return this.getAnnotation(CODE_GENERATION_URI, 'PythonCall'); + } + isRepeatable(node: SdsAnnotation | undefined): boolean { return hasAnnotationCallOf(node, this.Repeatable); } diff --git a/src/resources/builtins/safeds/lang/codeGeneration.sdsstub b/src/resources/builtins/safeds/lang/codeGeneration.sdsstub index 83b09a0e1..0508df295 100644 --- a/src/resources/builtins/safeds/lang/codeGeneration.sdsstub +++ b/src/resources/builtins/safeds/lang/codeGeneration.sdsstub @@ -24,3 +24,13 @@ annotation PythonModule( annotation PythonName( name: String ) + +/** + * The specification of a corresponding function call in Python. By default, the function is called as specified in the stub. + */ +@Target([ + AnnotationTarget.Function, +]) +annotation PythonCall( + callSpecification: String +) diff --git a/tests/resources/generation/expressions/call/input.sdstest b/tests/resources/generation/expressions/call/input.sdstest index 656a10dbe..23ded00e9 100644 --- a/tests/resources/generation/expressions/call/input.sdstest +++ b/tests/resources/generation/expressions/call/input.sdstest @@ -12,9 +12,21 @@ fun h( @PythonName("param_2") param2: Int = 0 ) -> result: Boolean +@PythonCall("$param.i()") +fun i(param: Any?) + +@PythonCall("$param.j($param2)") +fun j(param: Any?, param2: Any?) + +@PythonCall("k($param2, $param)") +fun k(param: Any?, param2: Any?) + pipeline test { f((g(1, 2))); f((g(param2 = 1, param1 = 2))); f((h(1, 2))); f((h(param2 = 1, param1 = 2))); + i("abc"); + j("abc", 123); + k(1.23, 456); } diff --git a/tests/resources/generation/expressions/call/output/tests/generator/call/gen_input.py b/tests/resources/generation/expressions/call/output/tests/generator/call/gen_input.py index e2968155b..1e10a4633 100644 --- a/tests/resources/generation/expressions/call/output/tests/generator/call/gen_input.py +++ b/tests/resources/generation/expressions/call/output/tests/generator/call/gen_input.py @@ -5,3 +5,6 @@ def test(): f(g(2, param2=1)) f(h(1, param_2=2)) f(h(2, param_2=1)) + 'abc'.i() + 'abc'.j(123) + k(456, 1.23) diff --git a/tests/resources/generation/expressions/member access/input.sdstest b/tests/resources/generation/expressions/member access/input.sdstest index 2c8f26592..71a924027 100644 --- a/tests/resources/generation/expressions/member access/input.sdstest +++ b/tests/resources/generation/expressions/member access/input.sdstest @@ -9,6 +9,8 @@ fun h() -> (result1: Boolean, result2: Boolean) class C() { attr a: Int @PythonName("c") attr b: Int + + @PythonCall("$param.i($this)") fun i(param: Any?) } fun factory() -> instance: C? @@ -21,4 +23,5 @@ pipeline test { f(C().b); f(factory()?.a); f(factory()?.b); + f(C().i(1)); } diff --git a/tests/resources/generation/expressions/member access/output/tests/generator/memberAccess/gen_input.py b/tests/resources/generation/expressions/member access/output/tests/generator/memberAccess/gen_input.py index 2d8cbe6ac..b508d00de 100644 --- a/tests/resources/generation/expressions/member access/output/tests/generator/memberAccess/gen_input.py +++ b/tests/resources/generation/expressions/member access/output/tests/generator/memberAccess/gen_input.py @@ -12,3 +12,4 @@ def test(): f(C().c) f(safeds_runner.codegen.safe_access(factory(), 'a')) f(safeds_runner.codegen.safe_access(factory(), 'c')) + f(1.i(C())) From 584bcf208d1201f31bf0812be498cbbe1142a63b Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:55:35 +0000 Subject: [PATCH 2/8] style: apply automated linter fixes --- src/cli/generator.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cli/generator.ts b/src/cli/generator.ts index 4399d295a..af0a07658 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -389,11 +389,21 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio return generatePythonCall(pythonCall, argumentsMap); } } - if (isSdsMemberAccess(expression.receiver) && isSdsReference(expression.receiver.member) && isSdsFunction(expression.receiver.member.target.ref)) { - const pythonCall = frame.getServices().builtins.Annotations.getPythonCall(expression.receiver.member.target.ref); + if ( + isSdsMemberAccess(expression.receiver) && + isSdsReference(expression.receiver.member) && + isSdsFunction(expression.receiver.member.target.ref) + ) { + const pythonCall = frame + .getServices() + .builtins.Annotations.getPythonCall(expression.receiver.member.target.ref); if (pythonCall) { const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); - return generatePythonCall(pythonCall, argumentsMap, generateExpression(expression.receiver.receiver, frame)); + return generatePythonCall( + pythonCall, + argumentsMap, + generateExpression(expression.receiver.receiver, frame), + ); } } const sortedArgs = sortArguments(frame.getServices(), expression.argumentList.arguments); From ed0ebe3cada67a0f192b6b4d284dadc6bc4412d6 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 23 Oct 2023 19:55:59 +0200 Subject: [PATCH 3/8] docs: add more documentation to `PythonCall` annotation --- src/language/validation/names.ts | 2 +- .../safeds/lang/codeGeneration.sdsstub | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/language/validation/names.ts b/src/language/validation/names.ts index 1b5b98fb5..a263d193e 100644 --- a/src/language/validation/names.ts +++ b/src/language/validation/names.ts @@ -235,7 +235,7 @@ export const moduleMemberMustHaveNameThatIsUniqueInPackage = (services: SafeDsSe let declarationsInPackage: AstNodeDescription[]; let kind: string; if (packageName.startsWith(BUILTINS_ROOT_PACKAGE)) { - // For a builtin package the simple names of declarations must be unique + // For a builtin package, the simple names of declarations must be unique declarationsInPackage = packageManager.getDeclarationsInPackageOrSubpackage(BUILTINS_ROOT_PACKAGE); kind = 'builtin declarations'; } else { diff --git a/src/resources/builtins/safeds/lang/codeGeneration.sdsstub b/src/resources/builtins/safeds/lang/codeGeneration.sdsstub index 0508df295..5e204a00b 100644 --- a/src/resources/builtins/safeds/lang/codeGeneration.sdsstub +++ b/src/resources/builtins/safeds/lang/codeGeneration.sdsstub @@ -1,5 +1,19 @@ package safeds.lang +/** + * The specification of a corresponding function call in Python. By default, the function is called as specified in the + * stub. + * + * @param callSpecification + * The specification of corresponding Python call. The specification can contain template expression, which are + * replaced by the corresponding arguments of the function call. `$this` is replaced by the receiver of the call. + * `$param` is replaced by the value of the parameter called `param`. Otherwise, the string is used as-is. + */ +@Target([AnnotationTarget.Function]) +annotation PythonCall( + callSpecification: String +) + /** * The qualified name of the corresponding Python module. By default, this is the qualified name of the package. */ @@ -24,13 +38,3 @@ annotation PythonModule( annotation PythonName( name: String ) - -/** - * The specification of a corresponding function call in Python. By default, the function is called as specified in the stub. - */ -@Target([ - AnnotationTarget.Function, -]) -annotation PythonCall( - callSpecification: String -) From ca1c0afd335d4eec07f8d28aefb18b912c51ba0d Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 23 Oct 2023 20:06:04 +0200 Subject: [PATCH 4/8] refactor: reorder some functions --- src/language/builtins/safe-ds-annotations.ts | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index 56028c464..003690474 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -66,8 +66,8 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { return this.getAnnotation(IDE_INTEGRATION_URI, 'Expert'); } - getPythonModule(node: SdsModule | undefined): string | undefined { - const value = this.getArgumentValue(node, this.PythonModule, 'qualifiedName'); + getPythonCall(node: SdsFunction | undefined): string | undefined { + const value = this.getArgumentValue(node, this.PythonCall, 'callSpecification'); if (value instanceof StringConstant) { return value.value; } else { @@ -75,12 +75,12 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } } - get PythonModule(): SdsAnnotation | undefined { - return this.getAnnotation(CODE_GENERATION_URI, 'PythonModule'); + get PythonCall(): SdsAnnotation | undefined { + return this.getAnnotation(CODE_GENERATION_URI, 'PythonCall'); } - getPythonName(node: SdsAnnotatedObject | undefined): string | undefined { - const value = this.getArgumentValue(node, this.PythonName, 'name'); + getPythonModule(node: SdsModule | undefined): string | undefined { + const value = this.getArgumentValue(node, this.PythonModule, 'qualifiedName'); if (value instanceof StringConstant) { return value.value; } else { @@ -88,12 +88,12 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } } - get PythonName(): SdsAnnotation | undefined { - return this.getAnnotation(CODE_GENERATION_URI, 'PythonName'); + get PythonModule(): SdsAnnotation | undefined { + return this.getAnnotation(CODE_GENERATION_URI, 'PythonModule'); } - getPythonCall(node: SdsFunction | undefined): string | undefined { - const value = this.getArgumentValue(node, this.PythonCall, 'callSpecification'); + getPythonName(node: SdsAnnotatedObject | undefined): string | undefined { + const value = this.getArgumentValue(node, this.PythonName, 'name'); if (value instanceof StringConstant) { return value.value; } else { @@ -101,8 +101,8 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } } - get PythonCall(): SdsAnnotation | undefined { - return this.getAnnotation(CODE_GENERATION_URI, 'PythonCall'); + get PythonName(): SdsAnnotation | undefined { + return this.getAnnotation(CODE_GENERATION_URI, 'PythonName'); } isRepeatable(node: SdsAnnotation | undefined): boolean { From 03c35d2cd4f0243567718c0a263fad5cb22afd5b Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 23 Oct 2023 20:07:07 +0200 Subject: [PATCH 5/8] refactor: inline a getter --- src/cli/generator.ts | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/cli/generator.ts b/src/cli/generator.ts index af0a07658..ffbf65122 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -210,7 +210,7 @@ const generateParameter = function ( frame: GenerationInfoFrame, defaultValue: boolean = true, ): string { - return expandToString`${getPythonNameOrDefault(frame.getServices(), parameter)}${ + return expandToString`${getPythonNameOrDefault(frame.services, parameter)}${ defaultValue && parameter.defaultValue !== undefined ? '=' + generateExpression(parameter.defaultValue, frame) : '' @@ -292,7 +292,7 @@ const generateStatement = function (statement: SdsStatement, frame: GenerationIn const generateAssignment = function (assignment: SdsAssignment, frame: GenerationInfoFrame): string { const requiredAssignees = isSdsCall(assignment.expression) - ? getAbstractResults(frame.getServices().helpers.NodeMapper.callToCallable(assignment.expression)).length + ? getAbstractResults(frame.services.helpers.NodeMapper.callToCallable(assignment.expression)).length : /* c8 ignore next */ 1; const assignees = getAssignees(assignment); @@ -348,7 +348,7 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio } } - const partiallyEvaluatedNode = frame.getServices().evaluation.PartialEvaluator.evaluate(expression); + const partiallyEvaluatedNode = frame.services.evaluation.PartialEvaluator.evaluate(expression); if (partiallyEvaluatedNode instanceof BooleanConstant) { return partiallyEvaluatedNode.value ? 'True' : 'False'; } else if (partiallyEvaluatedNode instanceof IntConstant) { @@ -383,7 +383,7 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio } if (isSdsCall(expression)) { if (isSdsReference(expression.receiver) && isSdsFunction(expression.receiver.target.ref)) { - const pythonCall = frame.getServices().builtins.Annotations.getPythonCall(expression.receiver.target.ref); + const pythonCall = frame.services.builtins.Annotations.getPythonCall(expression.receiver.target.ref); if (pythonCall) { const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); return generatePythonCall(pythonCall, argumentsMap); @@ -394,9 +394,7 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio isSdsReference(expression.receiver.member) && isSdsFunction(expression.receiver.member.target.ref) ) { - const pythonCall = frame - .getServices() - .builtins.Annotations.getPythonCall(expression.receiver.member.target.ref); + const pythonCall = frame.services.builtins.Annotations.getPythonCall(expression.receiver.member.target.ref); if (pythonCall) { const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); return generatePythonCall( @@ -406,7 +404,7 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio ); } } - const sortedArgs = sortArguments(frame.getServices(), expression.argumentList.arguments); + const sortedArgs = sortArguments(frame.services, expression.argumentList.arguments); return expandToString`${generateExpression(expression.receiver, frame)}(${sortedArgs .map((arg) => generateArgument(arg, frame)) .join(', ')})`; @@ -483,10 +481,10 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio if (isSdsReference(expression)) { const declaration = expression.target.ref!; const referenceImport = - getExternalReferenceNeededImport(frame.getServices(), expression, declaration) || - getInternalReferenceNeededImport(frame.getServices(), expression, declaration); + getExternalReferenceNeededImport(frame.services, expression, declaration) || + getInternalReferenceNeededImport(frame.services, expression, declaration); frame.addImport(referenceImport); - return referenceImport?.alias || getPythonNameOrDefault(frame.getServices(), declaration); + return referenceImport?.alias || getPythonNameOrDefault(frame.services, declaration); } /* c8 ignore next 2 */ throw new Error(`Unknown expression type: ${expression.$type}`); @@ -508,10 +506,7 @@ const generatePythonCall = function ( const getArgumentsMap = function (argumentList: SdsArgument[], frame: GenerationInfoFrame): Map { const argumentsMap = new Map(); argumentList.reduce((map, value) => { - map.set( - frame.getServices().helpers.NodeMapper.argumentToParameter(value)?.name!, - generateArgument(value, frame), - ); + map.set(frame.services.helpers.NodeMapper.argumentToParameter(value)?.name!, generateArgument(value, frame)); return map; }, argumentsMap); return argumentsMap; @@ -532,7 +527,7 @@ const sortArguments = function (services: SafeDsServices, argumentList: SdsArgum }; const generateArgument = function (argument: SdsArgument, frame: GenerationInfoFrame) { - const parameter = frame.getServices().helpers.NodeMapper.argumentToParameter(argument); + const parameter = frame.services.helpers.NodeMapper.argumentToParameter(argument); return expandToString`${ parameter !== undefined && !isRequiredParameter(parameter) ? generateParameter(parameter, frame, false) + '=' @@ -639,10 +634,6 @@ class GenerationInfoFrame { getUniqueLambdaBlockName(lambda: SdsBlockLambda): string { return `${BLOCK_LAMBDA_PREFIX}${this.blockLambdaManager.assignId(lambda)}`; } - - getServices(): SafeDsServices { - return this.services; - } } export interface GenerateOptions { From 8289421a45c30c68248f4b6afad2cbf2c36a3abd Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 23 Oct 2023 20:21:29 +0200 Subject: [PATCH 6/8] refactor: change visibility of fields --- src/cli/generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/generator.ts b/src/cli/generator.ts index ffbf65122..abe9f71f0 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -612,9 +612,9 @@ interface ImportData { } class GenerationInfoFrame { - services: SafeDsServices; - blockLambdaManager: IdManager; - importSet: Map; + readonly services: SafeDsServices; + private readonly blockLambdaManager: IdManager; + private readonly importSet: Map; constructor(services: SafeDsServices, importSet: Map = new Map()) { this.services = services; From 9a022a3c5acb178f64e7efc838049030151190af Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 23 Oct 2023 20:32:51 +0200 Subject: [PATCH 7/8] refactor: use `callToCallable` to get the corresponding function --- src/cli/generator.ts | 66 +++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/src/cli/generator.ts b/src/cli/generator.ts index abe9f71f0..adfc80dc5 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -361,61 +361,44 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio } else if (partiallyEvaluatedNode instanceof StringConstant) { return `'${formatStringSingleLine(partiallyEvaluatedNode.value)}'`; } - // Handled after constant expressions: EnumVariant, List, Map - if (isSdsTemplateString(expression)) { + // Handled after constant expressions: EnumVariant, List, Map + else if (isSdsTemplateString(expression)) { return `f'${expression.expressions.map((expr) => generateExpression(expr, frame)).join('')}'`; - } - - if (isSdsMap(expression)) { + } else if (isSdsMap(expression)) { const mapContent = expression.entries.map( (entry) => `${generateExpression(entry.key, frame)}: ${generateExpression(entry.value, frame)}`, ); return `{${mapContent.join(', ')}}`; - } - if (isSdsList(expression)) { + } else if (isSdsList(expression)) { const listContent = expression.elements.map((value) => generateExpression(value, frame)); return `[${listContent.join(', ')}]`; - } - - if (isSdsBlockLambda(expression)) { + } else if (isSdsBlockLambda(expression)) { return frame.getUniqueLambdaBlockName(expression); - } - if (isSdsCall(expression)) { - if (isSdsReference(expression.receiver) && isSdsFunction(expression.receiver.target.ref)) { - const pythonCall = frame.services.builtins.Annotations.getPythonCall(expression.receiver.target.ref); - if (pythonCall) { - const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); - return generatePythonCall(pythonCall, argumentsMap); - } - } - if ( - isSdsMemberAccess(expression.receiver) && - isSdsReference(expression.receiver.member) && - isSdsFunction(expression.receiver.member.target.ref) - ) { - const pythonCall = frame.services.builtins.Annotations.getPythonCall(expression.receiver.member.target.ref); + } else if (isSdsCall(expression)) { + const callable = frame.services.helpers.NodeMapper.callToCallable(expression); + if (isSdsFunction(callable)) { + const pythonCall = frame.services.builtins.Annotations.getPythonCall(callable); if (pythonCall) { + let thisParam: string | undefined = undefined; + if (isSdsMemberAccess(expression.receiver)) { + thisParam = generateExpression(expression.receiver.receiver, frame); + } const argumentsMap = getArgumentsMap(expression.argumentList.arguments, frame); - return generatePythonCall( - pythonCall, - argumentsMap, - generateExpression(expression.receiver.receiver, frame), - ); + return generatePythonCall(pythonCall, argumentsMap, thisParam); } } + const sortedArgs = sortArguments(frame.services, expression.argumentList.arguments); return expandToString`${generateExpression(expression.receiver, frame)}(${sortedArgs .map((arg) => generateArgument(arg, frame)) .join(', ')})`; - } - if (isSdsExpressionLambda(expression)) { + } else if (isSdsExpressionLambda(expression)) { return `lambda ${generateParameters(expression.parameterList, frame)}: ${generateExpression( expression.result, frame, )}`; - } - if (isSdsInfixOperation(expression)) { + } else if (isSdsInfixOperation(expression)) { const leftOperand = generateExpression(expression.leftOperand, frame); const rightOperand = generateExpression(expression.rightOperand, frame); switch (expression.operator) { @@ -435,14 +418,12 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio default: return `(${leftOperand}) ${expression.operator} (${rightOperand})`; } - } - if (isSdsIndexedAccess(expression)) { + } else if (isSdsIndexedAccess(expression)) { return expandToString`${generateExpression(expression.receiver, frame)}[${generateExpression( expression.index, frame, )}]`; - } - if (isSdsMemberAccess(expression)) { + } else if (isSdsMemberAccess(expression)) { const member = expression.member?.target.ref!; const receiver = generateExpression(expression.receiver, frame); if (isSdsEnumVariant(member)) { @@ -465,11 +446,9 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio return `${receiver}.${memberExpression}`; } } - } - if (isSdsParenthesizedExpression(expression)) { + } else if (isSdsParenthesizedExpression(expression)) { return expandToString`${generateExpression(expression.expression, frame)}`; - } - if (isSdsPrefixOperation(expression)) { + } else if (isSdsPrefixOperation(expression)) { const operand = generateExpression(expression.operand, frame); switch (expression.operator) { case 'not': @@ -477,8 +456,7 @@ const generateExpression = function (expression: SdsExpression, frame: Generatio case '-': return expandToString`-(${operand})`; } - } - if (isSdsReference(expression)) { + } else if (isSdsReference(expression)) { const declaration = expression.target.ref!; const referenceImport = getExternalReferenceNeededImport(frame.services, expression, declaration) || From 43aceaade02cbb7ea44e04d520496d2e6edf5c9e Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 23 Oct 2023 20:40:22 +0200 Subject: [PATCH 8/8] feat: use more restrictive regex to match template expressions --- src/cli/generator.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cli/generator.ts b/src/cli/generator.ts index adfc80dc5..2d64c035a 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -476,9 +476,8 @@ const generatePythonCall = function ( if (thisParam) { argumentsMap.set('this', thisParam); } - // Extract each placeholder from annotation: Match only strings that start with '$' - // Use look-ahead to only match up to a '.', ',', ')' or a whitespace - return pythonCall.replace(/\$.+?(?=[\s.,)])/gu, (value) => argumentsMap.get(value.substring(1))!); + + return pythonCall.replace(/\$[_a-zA-Z][_a-zA-Z0-9]*/gu, (value) => argumentsMap.get(value.substring(1))!); }; const getArgumentsMap = function (argumentList: SdsArgument[], frame: GenerationInfoFrame): Map {