Skip to content

Commit 8d33929

Browse files
committed
#871 Add ParentGroupSetter AST transformer to fix stale parent references after AST transformations
This required making `parent` mutable in `Group` and `Primitive`.
1 parent 517de0a commit 8d33929

5 files changed

Lines changed: 71 additions & 5 deletions

File tree

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/Copybook.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,8 @@ object Copybook {
411411
}
412412

413413
// recompute sizes
414-
val schema = BinaryPropertiesAdder().transform(newRoot)
414+
val schema1 = BinaryPropertiesAdder().transform(newRoot)
415+
val schema = ParentGroupSetter().transform(schema1)
415416

416417
new Copybook(schema)
417418
}

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/CopybookParser.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,21 @@ object CopybookParser extends Logging {
332332
// For each group calculates the number of non-filler items.
333333
NonFillerCountSetter(),
334334
// Sets isUsedInRules and rule expressions for each field
335-
RuleExpressionSetter(redefineRuleExpressions)
335+
RuleExpressionSetter(redefineRuleExpressions),
336+
// Updates 'parent' field of each of the fields in case they are inconsistent
337+
ParentGroupSetter()
336338
)
337339

338340
val transformedAst = transformers.foldLeft(schemaANTLR) {
339341
(ast, transformer) => transformer.transform(ast)
340342
}
341343

342-
new Copybook(transformedAst)
344+
val finalAst = if (transformers.nonEmpty)
345+
ParentGroupSetter().transform(transformedAst)
346+
else
347+
transformedAst
348+
349+
new Copybook(finalAst)
343350
}
344351

345352
/**

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/Group.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ case class Group(
6363
ruleExpression: Option[ExpressionEvaluator] = None,
6464
binaryProperties: BinaryProperties = BinaryProperties(0, 0, 0)
6565
)
66-
(val parent: Option[Group] = None)
66+
(var parent: Option[Group] = None)
6767
extends Statement {
6868

6969
/** This method is used to add a [[za.co.absa.cobrix.cobol.parser.ast.Statement]] object as a child of

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/Primitive.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ case class Primitive(
6161
encode: Option[EncoderSelector.Encoder],
6262
binaryProperties: BinaryProperties = BinaryProperties(0, 0, 0)
6363
)
64-
(val parent: Option[Group] = None)
64+
(var parent: Option[Group] = None)
6565
extends Statement {
6666

6767
/** This is cached value specifying if the field is a string */
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2018 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package za.co.absa.cobrix.cobol.parser.asttransform
18+
19+
import za.co.absa.cobrix.cobol.parser.CopybookParser.CopybookAST
20+
import za.co.absa.cobrix.cobol.parser.ast.{Group, Primitive}
21+
22+
/**
23+
* An AST transformer that restores the parent references of every element of a copybook AST.
24+
*
25+
* The AST is traversed recursively from the root group down to the leaves, and each group and primitive
26+
* field is recreated so that its `parent` points to the newly created copy of its enclosing group. The root
27+
* of the AST is left without a parent.
28+
*
29+
* This transformation is required because AST elements are immutable case classes: whenever a copybook AST is
30+
* rebuilt or modified by other transformations, the parent links of the affected elements may point to stale
31+
* copies of their parents. Applying this transformer as the final step ensures that the parent links are
32+
* consistent with the actual hierarchy of the resulting AST.
33+
*
34+
* The structure, ordering and all other properties of the fields remain unchanged.
35+
*/
36+
class ParentGroupSetter extends AstTransformer {
37+
final override def transform(ast: CopybookAST): CopybookAST = {
38+
def processGroup(group: Group, parent: Option[Group]): Unit = {
39+
var i = 0
40+
group.parent = parent
41+
while (i < group.children.length) {
42+
group.children(i) match {
43+
case g: Group => processGroup(g, Some(group))
44+
case p: Primitive => p.parent = Some(group)
45+
}
46+
i += 1
47+
}
48+
}
49+
50+
processGroup(ast, None)
51+
ast
52+
}
53+
}
54+
55+
56+
object ParentGroupSetter {
57+
def apply(): ParentGroupSetter = new ParentGroupSetter()
58+
}

0 commit comments

Comments
 (0)