diff --git a/cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/antlr/ParserVisitor.scala b/cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/antlr/ParserVisitor.scala index 70e2caff..c0aee5a6 100644 --- a/cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/antlr/ParserVisitor.scala +++ b/cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/antlr/ParserVisitor.scala @@ -503,14 +503,14 @@ class ParserVisitor(enc: Encoding, val section = ctx.section.getText.toInt val parent: Group = getParentFromLevel(section) - assert(ctx.redefines.size() < 2) - assert(ctx.usageGroup.size() < 2) - assert(ctx.occurs.size() < 2) - assert(ctx.values.size() < 2) - val identifier = visitIdentifier(ctx.identifier()).value val originalIdentifier = visitIdentifier(ctx.identifier()).originalValue + require(ctx.redefines.size() < 2, ctx, Option(identifier), "Duplicate REDEFINES clause") + require(ctx.usageGroup.size() < 2, ctx, Option(identifier), "Duplicate USAGE clause") + require(ctx.occurs.size() < 2, ctx, Option(identifier), "Duplicate OCCURS clause") + require(ctx.values.size() < 2, ctx, Option(identifier), "Duplicate VALUE clause") + val redefines: Option[String] = ctx.redefines().asScala.toList match { case Nil => None case x :: _ => Some(visitIdentifier(x.identifier).value) @@ -793,15 +793,17 @@ class ParserVisitor(enc: Encoding, val section = ctx.section.getText.toInt val parent: Group = getParentFromLevel(section) - assert(ctx.redefines.size() < 2) - assert(ctx.usage.size() < 2) - assert(ctx.occurs.size() < 2) - assert(ctx.separateSign.size() < 2) - assert(ctx.pic.size() == 1) - val identifier = visitIdentifier(ctx.identifier()).value val originalIdentifier = visitIdentifier(ctx.identifier()).originalValue + require(ctx.redefines.size() < 2, ctx, Option(identifier), "Duplicate REDEFINES clause") + require(ctx.usage.size() < 2, ctx, Option(identifier), "Duplicate USAGE clause") + require(ctx.occurs.size() < 2, ctx, Option(identifier), "Duplicate OCCURS clause") + require(ctx.separateSign.size() < 2, ctx, Option(identifier), "Duplicate SEPARATE SIGN clause") + require(ctx.values.size() < 2, ctx, Option(identifier), "Duplicate VALUE clause") + require(ctx.pic.size() < 2, ctx, Option(identifier), "Duplicate PIC clauses") + require(ctx.pic.size() > 0, ctx, Option(identifier), "Missing PIC clauses") + var pic: PicExpr = visitPic(ctx.pic(0)) val redefines: Option[String] = ctx.redefines().asScala.toList match { @@ -874,9 +876,9 @@ class ParserVisitor(enc: Encoding, override def visitLevel66statement(ctx: copybookParser.Level66statementContext): Expr = { throw new RuntimeException("Renames not supported yet") } -} - - - - + def require(condition: => Boolean, ctx: ParserRuleContext, field: Option[String], message: String): Unit = { + if (!condition) { + throw new SyntaxErrorException(ctx.start.getLine, None, field, message) + } + }} diff --git a/cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/parse/SyntaxErrorsSpec.scala b/cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/parse/SyntaxErrorsSpec.scala index 5b602a18..4f940fe8 100644 --- a/cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/parse/SyntaxErrorsSpec.scala +++ b/cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/parse/SyntaxErrorsSpec.scala @@ -19,7 +19,6 @@ package za.co.absa.cobrix.cobol.parser.parse import org.scalatest.funsuite.AnyFunSuite import org.slf4j.{Logger, LoggerFactory} import za.co.absa.cobrix.cobol.base.SimpleComparisonBase -import za.co.absa.cobrix.cobol.internal.Logging import za.co.absa.cobrix.cobol.parser.CopybookParser import za.co.absa.cobrix.cobol.parser.ast.{Group, Primitive} import za.co.absa.cobrix.cobol.parser.exceptions.SyntaxErrorException @@ -390,4 +389,171 @@ class SyntaxErrorsSpec extends AnyFunSuite with SimpleComparisonBase { assert(syntaxErrorException.lineNumber == 3) assert(syntaxErrorException.getMessage.toUpperCase.contains("REDEFINES")) } + + test("The multiple REDEFINES clause in primitive") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC X(1). + | 10 FILED3 REDEFINES FILED1 REDEFINES FILED2 PIC X(1). + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 4) + assert(syntaxErrorException.fieldOpt.contains("FILED3")) + assert(syntaxErrorException.getMessage.contains("Duplicate REDEFINES clause")) + } + + test("The multiple REDEFINES clause in group") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC X(1). + | 10 FILED3 REDEFINES FILED1 REDEFINES FILED2. + | 15 FIELD4 PIC X(1). + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 4) + assert(syntaxErrorException.fieldOpt.contains("FILED3")) + assert(syntaxErrorException.getMessage.contains("Duplicate REDEFINES clause")) + } + + test("The multiple OCCURS clause in primitive") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC X(1) OCCURS 2 TIMES OCCURS 3 TIMES. + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate OCCURS clause")) + } + + test("The multiple OCCURS clause in group") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 OCCURS 2 TIMES OCCURS 3 TIMES. + | 15 FIELD3 PIC X(1). + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate OCCURS clause")) + } + + test("The multiple USAGE clause in group") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 USAGE COMP USAGE COMP. + | 15 FIELD3 PIC 9(4). + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate USAGE clause")) + } + + test("The multiple SIGN SEPARATE clause in primitive") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC S9(4) SIGN IS LEADING SEPARATE + | SIGN IS TRAILING SEPARATE. + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate SEPARATE SIGN clause")) + } + + test("The multiple USAGE clause in primitive") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC X(1) USAGE COMP USAGE COMP. + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate USAGE clause")) + } + + test("The multiple PIC clause in primitive") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC X(1) PIC X(2). + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate PIC clause")) + } + + test("The multiple VALUE clause in primitive") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 PIC X(1) VALUE 'A' VALUE 'B'. + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate VALUE clause")) + } + + test("The multiple VALUE clause in group") { + val copyBookContents = + """ 01 RECORD. + | 10 FILED1 PIC X(1). + | 10 FILED2 VALUE 'A' VALUE 'B'. + |""".stripMargin + + val syntaxErrorException = intercept[SyntaxErrorException] { + CopybookParser.parseTree(copyBookContents) + } + + assert(syntaxErrorException.lineNumber == 3) + assert(syntaxErrorException.fieldOpt.contains("FILED2")) + assert(syntaxErrorException.getMessage.contains("Duplicate VALUE clause")) + } + }