Skip to content

Commit

Permalink
Merge pull request #675 from smeup/feature/LS24005158/fix-params-assi…
Browse files Browse the repository at this point in the history
…gnment-at-the-end-of-program-called

Bugfix/LS24005158/Fix params assignment at the end of program called
  • Loading branch information
lanarimarco authored Dec 6, 2024
2 parents ea2dbef + 763ff75 commit 66bb0dd
Show file tree
Hide file tree
Showing 25 changed files with 681 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ open class InternalInterpreter(
}
initialValue
}
/*
* In accord to documentation (see https://www.ibm.com/docs/en/i/7.5?topic=codes-plist-identify-parameter-list):
* when control transfers to called program, at the beginning, the contents of the Result field is placed in
* the Factor 1 field.
*/
it.isInPlist(compilationUnit) -> {
val resultName = it.getResultNameByFactor1(compilationUnit)
if (resultName == null || initialValues[resultName] is NullValue) {
blankValue(it)
} else {
initialValues[resultName]
}
}

it.initializationValue != null -> eval(it.initializationValue)
it.isCompileTimeArray() -> toArrayValue(
Expand Down Expand Up @@ -384,6 +397,45 @@ open class InternalInterpreter(
}
}

/**
* Retrieves the result name associated with the current `AbstractDataDefinition` instance
* from the parameter list (PList) of the specified `CompilationUnit`.
*
* This function searches the PList for the first parameter where `factor1` is of type `DataRefExpr`
* and its variable name matches the name of the current `AbstractDataDefinition` (case-insensitively).
* If such a parameter is found, its associated result name is returned.
*
* @param compilationUnit the compilation unit whose entry PList is to be checked
* @return the result name associated with the matching parameter, or `null` if no match is found
*/
private fun AbstractDataDefinition.getResultNameByFactor1(compilationUnit: CompilationUnit): String? {
val resultName = compilationUnit.entryPlist?.params
?.filter { plistParam -> plistParam.factor1 is DataRefExpr }
?.firstOrNull { plistParamFiltered ->
(plistParamFiltered.factor1 as DataRefExpr).variable.name.equals(
this.name,
true
)
}
?.result?.name
return resultName
}

/**
* Checks if the current `AbstractDataDefinition` instance is present in the parameter list (PList)
* of the specified `CompilationUnit`.
*
* This function evaluates whether the `AbstractDataDefinition` matches any parameter in the PList
* by comparing their names (case-insensitively). Parameters in the PList are filtered to include
* only those with a `factor1` of type `DataRefExpr`.
*
* @param compilationUnit the compilation unit whose entry PList is to be checked
* @return `true` if the `AbstractDataDefinition` is present in the PList, otherwise `false`
*/
private fun AbstractDataDefinition.isInPlist(compilationUnit: CompilationUnit) = compilationUnit.entryPlist?.params
?.filter { plistParam -> plistParam.factor1 is DataRefExpr }
?.any { plistParamFiltered -> (plistParamFiltered.factor1 as DataRefExpr).variable.name.equals(this.name, true) } == true

private fun toArrayValue(compileTimeArray: CompileTimeArray, arrayType: ArrayType): Value {
// It is not clear why the compileTimeRecordsPerLine on the array type is null
// probably it is an error during the ast processing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ class RpgProgram(val cu: CompilationUnit, val name: String = "<UNNAMED RPG PROGR
val plistParams = cu.entryPlist
// TODO derive proper type from the data specification
return plistParams?.params?.map {
val type = cu.getAnyDataDefinition(it.param.name) {
"Cannot resolve PARAM: ${it.param.name} in *ENTRY PLIST of the program: $name"
val type = cu.getAnyDataDefinition(it.result.name) {
"Cannot resolve RESULT: ${it.result.name} in *ENTRY PLIST of the program: $name"
}.type
ProgramParam(it.param.name, type)
ProgramParam(it.result.name, type)
}
?: emptyList()
}
Expand Down Expand Up @@ -135,6 +135,7 @@ class RpgProgram(val cu: CompilationUnit, val name: String = "<UNNAMED RPG PROGR
"param ${pv.key} was expected to have type $expectedType. It has value: $coercedValue"
}
}

if (!initialized) {
initialized = true

Expand Down Expand Up @@ -185,7 +186,14 @@ class RpgProgram(val cu: CompilationUnit, val name: String = "<UNNAMED RPG PROGR
null
)
params.keys.forEach { params[it] = interpreter[it] }
changedInitialValues = params().map { interpreter[it.name] }

/* In accord to documentation (see https://www.ibm.com/docs/en/i/7.5?topic=codes-plist-identify-parameter-list):
* at the end, if the Factor 2 is declared, replaces the Result with the value of Factor 2.
*/
changedInitialValues = params().map { param -> this.cu.entryPlist?.params
?.firstOrNull { plistParamCu -> plistParamCu.result.name.equals(param.name, true) }
.let { it?.factor2?.let { factor2 -> interpreter.eval(factor2) } } ?: interpreter[param.name] }

// here clear symbol table if needed
interpreter.doSomethingAfterExecution()
}.nanoseconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -935,16 +935,16 @@ data class CallStmt(
if (it.dataDefinition != null) {
// handle declaration of new variable
if (it.dataDefinition.initializationValue != null) {
if (!interpreter.exists(it.param.name)) {
if (!interpreter.exists(it.result.name)) {
interpreter.assign(it.dataDefinition, interpreter.eval(it.dataDefinition.initializationValue))
} else {
interpreter.assign(
interpreter.dataDefinitionByName(it.param.name)!!,
interpreter.dataDefinitionByName(it.result.name)!!,
interpreter.eval(it.dataDefinition.initializationValue)
)
}
} else {
if (!interpreter.exists(it.param.name)) {
if (!interpreter.exists(it.result.name)) {
interpreter.assign(it.dataDefinition, interpreter.eval(BlanksRefExpr(it.position)))
}
}
Expand All @@ -953,12 +953,12 @@ data class CallStmt(
// change the value of parameter with initialization value
if (it.initializationValue != null) {
interpreter.assign(
interpreter.dataDefinitionByName(it.param.name)!!,
interpreter.dataDefinitionByName(it.result.name)!!,
interpreter.eval(it.initializationValue)
)
}
}
targetProgramParams[index].name to interpreter[it.param.name]
targetProgramParams[index].name to interpreter[it.result.name]
}.toMap(LinkedHashMap())

val paramValuesAtTheEnd =
Expand Down Expand Up @@ -989,10 +989,10 @@ data class CallStmt(
paramValuesAtTheEnd?.forEachIndexed { index, value ->
if (this.params.size > index) {
val currentParam = this.params[index]
interpreter.assign(currentParam.param.referred!!, value)
interpreter.assign(currentParam.result.referred!!, value)

// If we also have a result field, assign to it
currentParam.result?.let { interpreter.assign(it, value) }
currentParam.factor1?.let { interpreter.assign(it, value) }
}
}

Expand Down Expand Up @@ -1344,8 +1344,8 @@ data class PlistStmt(

override fun execute(interpreter: InterpreterCore) {
params.forEach {
if (interpreter.getGlobalSymbolTable().contains(it.param.name)) {
interpreter.getGlobalSymbolTable()[it.param.name]
if (interpreter.getGlobalSymbolTable().contains(it.result.name)) {
interpreter.getGlobalSymbolTable()[it.result.name]
}
}
}
Expand All @@ -1360,8 +1360,9 @@ data class PlistStmt(

@Serializable
data class PlistParam(
val result: AssignableExpression?,
val param: ReferenceByName<AbstractDataDefinition>,
val factor1: AssignableExpression?,
val factor2: Expression?,
val result: ReferenceByName<AbstractDataDefinition>,
// TODO @Derived????
@Derived val dataDefinition: InStatementDataDefinition? = null,
override val position: Position? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1157,29 +1157,41 @@ internal fun CsPLISTContext.toAst(conf: ToAstConfiguration = ToAstConfiguration(
}

internal fun CsPARMContext.toAst(conf: ToAstConfiguration = ToAstConfiguration()): PlistParam {
var paramName = this.cspec_fixed_standard_parts().result.text
if (paramName.contains(".")) {
val parts = paramName.split(".")
var resultName = this.cspec_fixed_standard_parts().result.text
if (resultName.contains(".")) {
val parts = resultName.split(".")
require(parts.isNotEmpty())
paramName = parts.last()
}
// initialization value valid only if there isn't a variable declaration
val initializationValue = if (this.cspec_fixed_standard_parts().len.asInt() == null) {
this.cspec_fixed_standard_parts().factor2Expression(conf)
} else {
null
resultName = parts.last()
}
val resultPosition = this.cspec_fixed_standard_parts().result.toPosition()

val factor1Text = this.factor1.text.trim()
val factor1Position = this.factor1.toPosition(conf.considerPosition)
val result = if (factor1Text.isNotEmpty()) annidatedReferenceExpression(factor1Text, factor1Position) else null

val factor1Expression = if (factor1Text.isNotEmpty()) annidatedReferenceExpression(factor1Text, factor1Position) else null
val factor2Expression = this.cspec_fixed_standard_parts().factor2Expression(conf)

/*
* In accord to documentation (see https://www.ibm.com/docs/en/i/7.5?topic=codes-plist-identify-parameter-list):
* - when `CALL` is processed, the content of Factor 2 is placed in the Result field. So. is considered Factor 2 value;
* - when control transfers to called program, the contents of the Result field is placed in
* the Factor 1 field. So, is considered Result Value.
*/
val initializationValue = if (this.parent is CsCALLContext && this.cspec_fixed_standard_parts().len.asInt() == null) {
factor2Expression
} else if (this.parent is CsPLISTContext && this.cspec_fixed_standard_parts().len.asInt() == null) {
annidatedReferenceExpression(resultName, resultPosition)
} else {
null
}

val position = toPosition(conf.considerPosition)
return PlistParam(
result,
ReferenceByName(paramName),
factor1Expression,
factor2Expression,
ReferenceByName(resultName),
this.cspec_fixed_standard_parts().toDataDefinition(
paramName,
resultName,
position,
conf
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ private fun CompilationUnit.resolve() {
}

this.specificProcess(PlistParam::class.java) { pp ->
if (!pp.param.resolved) {
pp.param.tryToResolveRecursively(position = pp.position, cu = this)
if (!pp.result.resolved) {
pp.result.tryToResolveRecursively(position = pp.position, cu = this)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ class JarikoCallbackTest : AbstractTest() {
@Test
fun executeERROR08CallBackTest() {
// Errors in block statements
executePgmCallBackTest("ERROR08", SourceReferenceType.Program, "ERROR08", listOf(7, 8, 9, 14, 15, 16, 21, 22))
executePgmCallBackTest("ERROR08", SourceReferenceType.Program, "ERROR08", listOf(7, 8, 8, 9, 9, 14, 15, 15, 16, 16, 21, 22, 22))
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,4 +673,111 @@ open class MULANGT10BaseCodopTest : MULANGTTest() {
val expected = listOf("ok")
assertEquals(expected, "smeup/MUDRNRAPU00274".outputOf(configuration = smeupConfig))
}

/**
* This program tests the moving of value from Factor 2 to Result. This operation is performed at the end of execution of
* program called. In this case, MUDRNRAPU00172_P turn on indicator 35. Later, `DO` block ends its execution.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00172() {
val expected = listOf("Sub program", "1")
assertEquals(expected, "smeup/MUDRNRAPU00172".outputOf(configuration = smeupConfig))
}

/**
* This program tests the behaviour of `CALL` and `PLIST` when is used the Params for both between caller and called.
* In accord to documentation:
* - when `CALL` is processed, the content of Factor 2 is placed in the Result field;
* - when control transfers to called program, the contents of the Result field is placed in the Factor 1 field.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00173() {
val expected = listOf("BAR")
assertEquals(expected, "smeup/MUDRNRAPU00173".outputOf(configuration = smeupConfig))
}

/**
* This program tests the behaviour of `CALL` and `PLIST` when is used the Params for both between caller and called.
* Also, the called program change the result (`RES`) to another value.
* In accord to documentation:
* - when `CALL` is processed, the content of Factor 2 is placed in the Result field;
* - when control transfers to called program, the contents of the Result field is placed in the Factor 1 field.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00174() {
val expected = listOf("BAR")
assertEquals(expected, "smeup/MUDRNRAPU00174".outputOf(configuration = smeupConfig))
}

/**
* This program is more complex. Tests the assignment of value between waterfall calls and by parameters. Each subprogram is
* called max 101 times (last is Jariko error), thanks `DO` statement; this main program calls `MUDRNRAPU00175_P1` which
* calls recursively `MUDRNRAPU00175_P2`.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00175() {
val expected = listOf("HELLO")
assertEquals(expected, "smeup/MUDRNRAPU00175".outputOf(configuration = smeupConfig))
}

/**
* This program is more complex. Tests the assignment of value between waterfall calls and by using `EVAL`.
* Each subprogram is called max 101 times (last is Jariko error), thanks `DO` statement; this main program calls
* `MUDRNRAPU00176_P1` which calls recursively `MUDRNRAPU00176_P2`.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00176() {
val expected = listOf("HELLO")
assertEquals(expected, "smeup/MUDRNRAPU00176".outputOf(configuration = smeupConfig))
}

/**
* This program call a sub program by using pre-initialized variables as factors and result. The called program
* doesn't make any assignment.
* This tests the full behaviour between a CALLER and CALLED, where:
* - caller (at the beginning) move Factor 2 to Result;
* - called (at the beginning) move Result to Factor 1;
* - called (at the end) move Factor 2 to Result;
* - caller (at the end) move Result to Factor 1.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00177() {
val expected = listOf("CALLED", "BAR", "", "BAR", "CALLER", "", "BAR", "")
assertEquals(expected, "smeup/MUDRNRAPU00177".outputOf(configuration = smeupConfig))
}

/**
* This program call a sub program by using pre-initialized variables as factors and result. The called program
* doesn't make any assignment.
* This tests the full behaviour between a CALLER and CALLED, where:
* - caller (at the beginning) move Factor 2 to Result;
* - called (at the beginning) move Result to Factor 1;
* - caller (at the end) move Result to Factor 1.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00178() {
val expected = listOf("CALLED", "BAR", "BAR", "CALLER", "BAR", "BAR", "BAR")
assertEquals(expected, "smeup/MUDRNRAPU00178".outputOf(configuration = smeupConfig))
}

/**
* This program call a sub program by using pre-initialized variables as factors and result. The called program
* doesn't make any assignment.
* This tests the full behaviour between a CALLER and CALLED, where:
* - called (at the beginning) move Result to Factor 1;
* - caller (at the end) move Result to Factor 1.
* @see #LS24005158
*/
@Test
fun executeMUDRNRAPU00179() {
val expected = listOf("CALLED", "BAZ", "BAZ", "CALLER", "BAZ", "BAZ")
assertEquals(expected, "smeup/MUDRNRAPU00179".outputOf(configuration = smeupConfig))
}
}
Loading

0 comments on commit 66bb0dd

Please sign in to comment.