diff --git a/kolasu/src/main/kotlin/com/strumenta/kolasu/model/Processing.kt b/kolasu/src/main/kotlin/com/strumenta/kolasu/model/Processing.kt index 6e8933725..8ceef98d3 100644 --- a/kolasu/src/main/kotlin/com/strumenta/kolasu/model/Processing.kt +++ b/kolasu/src/main/kotlin/com/strumenta/kolasu/model/Processing.kt @@ -191,7 +191,7 @@ fun Node.transformChildren(operation: (Node) -> Node, inPlace: Boolean = false): } } var instanceToTransform = this - if (!changes.isEmpty()) { + if (changes.isNotEmpty()) { val constructor = this.javaClass.kotlin.primaryConstructor!! val params = HashMap() constructor.parameters.forEach { param -> @@ -206,9 +206,8 @@ fun Node.transformChildren(operation: (Node) -> Node, inPlace: Boolean = false): return instanceToTransform } -fun Node.replace(other: Node) { - if (this.parent == null) { - throw IllegalStateException("Parent not set") - } - this.parent!!.transformChildren(inPlace = true, operation = { if (it == this) other else it }) +fun Node.replace(other: Node): Node { + return this.parent?.let { + it.transformChildren(inPlace = true, operation = { node -> if (node == this) other else node }) + } ?: throw IllegalStateException("Parent not set") } diff --git a/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/ast/statements.kt b/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/ast/statements.kt index 0cb477e38..1a57dce6c 100644 --- a/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/ast/statements.kt +++ b/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/ast/statements.kt @@ -816,7 +816,7 @@ data class CallStmt( @Serializable data class CallPStmt( - val functionCall: FunctionCall, + var functionCall: FunctionCall, val errorIndicator: IndicatorKey? = null, override val position: Position? = null ) : Statement(position), StatementThatCanDefineData { diff --git a/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/parsetreetoast/resolution.kt b/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/parsetreetoast/resolution.kt index 98c8eb80d..6f3b70ced 100644 --- a/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/parsetreetoast/resolution.kt +++ b/rpgJavaInterpreter-core/src/main/kotlin/com/smeup/rpgparser/parsing/parsetreetoast/resolution.kt @@ -123,16 +123,35 @@ private fun Node.resolveDataRefs(cu: CompilationUnit) { private fun Node.resolveFunctionCalls(cu: CompilationUnit) { // replace FunctionCall with ArrayAccessExpr where it makes sense this.specificProcess(FunctionCall::class.java) { fc -> - if (fc.args.size == 1) { - val data = cu.allDataDefinitions.firstOrNull { it.name == fc.function.name } - if (data != null) { - fc.replace(ArrayAccessExpr( - array = DataRefExpr(ReferenceByName(fc.function.name, referred = data)), - index = fc.args[0], - position = fc.position)) - } + fc.tryReplaceWithArrayAccess(cu) + } +} + +private fun FunctionCall.tryReplaceWithArrayAccess(cu: CompilationUnit): Optional { + // Only said FunctionCalls with 1 arg can be ArrayAccessExpr + if (this.args.size != 1) return Optional.empty() + + // Replacement can only happen when there is a DataDefinition named like this 'FunctionCall' + val data = cu.allDataDefinitions.firstOrNull { it.name == this.function.name } + data ?: return Optional.empty() + + // Recursively try to process inner expressions + var indexExpr = this.args.first() + if (indexExpr is FunctionCall) { + indexExpr.tryReplaceWithArrayAccess(cu).ifPresent { + // Needed for type-checking + if (it is Expression) indexExpr = it } } + + val arrayAccessExpr = ArrayAccessExpr( + array = DataRefExpr(ReferenceByName(this.function.name, referred = data)), + index = indexExpr, + position = this.position + ) + + val newExpression = this.replace(arrayAccessExpr).children.first() + return Optional.of(newExpression) } fun MuteAnnotation.resolveAndValidate(cu: CompilationUnit) { diff --git a/rpgJavaInterpreter-core/src/test/kotlin/com/smeup/rpgparser/smeup/MULANGT02ConstAndDSpecTest.kt b/rpgJavaInterpreter-core/src/test/kotlin/com/smeup/rpgparser/smeup/MULANGT02ConstAndDSpecTest.kt index 951fb62fe..a5138515f 100644 --- a/rpgJavaInterpreter-core/src/test/kotlin/com/smeup/rpgparser/smeup/MULANGT02ConstAndDSpecTest.kt +++ b/rpgJavaInterpreter-core/src/test/kotlin/com/smeup/rpgparser/smeup/MULANGT02ConstAndDSpecTest.kt @@ -539,6 +539,16 @@ open class MULANGT02ConstAndDSpecTest : MULANGTTest() { assertEquals(expected, "smeup/MUDRNRAPU00243".outputOf(configuration = smeupConfig)) } + /** + * Access to an array detected as a function call recursively + * @see #LS24003753 + */ + @Test + fun executeMUDRNRAPU00244() { + val expected = listOf("ok") + assertEquals(expected, "smeup/MUDRNRAPU00244".outputOf(configuration = smeupConfig)) + } + /** * Resolution of InStatement data definitions contained in CompositeStatements * @see #LS24003769 diff --git a/rpgJavaInterpreter-core/src/test/resources/smeup/MUDRNRAPU00244.rpgle b/rpgJavaInterpreter-core/src/test/resources/smeup/MUDRNRAPU00244.rpgle new file mode 100644 index 000000000..26145fe3a --- /dev/null +++ b/rpgJavaInterpreter-core/src/test/resources/smeup/MUDRNRAPU00244.rpgle @@ -0,0 +1,23 @@ + * Helper declarations + D £DBG_Str S 2 + + * Declarations needed for test purpose + D £C6JDSO DS 512 + D £C6JO_TO 2 0 DIM(10) + D $X S 5 0 + D §§IMPO S 6 0 + D T$AG_I S 21 6 DIM(15) + + * Initializing array indices to avoid null value exception + C EVAL $X=2 + C EVAL £C6JO_TO($X)=2 + + * Trying to access an array while accessing another array (like £C6JO_TO($X) inside T$AG_I). + * £C6JO_TO($X) should not be seen as a function call but as an array access. + * This should be true when this kind of array access is contained in any type expression. + C EVAL T$AG_I(£C6JO_TO($X))= + C T$AG_I(£C6JO_TO($X))+§§IMPO + + * Test output + C EVAL £DBG_Str='ok' + C £DBG_Str DSPLY