001/*-------------------------------------------------------------------------+ 002| | 003| Copyright (c) 2009-2017 CQSE GmbH | 004| | 005+-------------------------------------------------------------------------*/ 006package eu.cqse.check.framework.shallowparser.languages.cobol; 007 008import static eu.cqse.check.framework.scanner.ETokenType.ACCEPT; 009import static eu.cqse.check.framework.scanner.ETokenType.ADD; 010import static eu.cqse.check.framework.scanner.ETokenType.AT; 011import static eu.cqse.check.framework.scanner.ETokenType.CALL; 012import static eu.cqse.check.framework.scanner.ETokenType.COMPUTE; 013import static eu.cqse.check.framework.scanner.ETokenType.DELETE; 014import static eu.cqse.check.framework.scanner.ETokenType.DISPLAY; 015import static eu.cqse.check.framework.scanner.ETokenType.DIVIDE; 016import static eu.cqse.check.framework.scanner.ETokenType.END; 017import static eu.cqse.check.framework.scanner.ETokenType.END_OF_PAGE; 018import static eu.cqse.check.framework.scanner.ETokenType.EOP; 019import static eu.cqse.check.framework.scanner.ETokenType.ERROR; 020import static eu.cqse.check.framework.scanner.ETokenType.ESCAPE; 021import static eu.cqse.check.framework.scanner.ETokenType.EXCEPTION; 022import static eu.cqse.check.framework.scanner.ETokenType.INVALID; 023import static eu.cqse.check.framework.scanner.ETokenType.KEY; 024import static eu.cqse.check.framework.scanner.ETokenType.MULTIPLY; 025import static eu.cqse.check.framework.scanner.ETokenType.NOT; 026import static eu.cqse.check.framework.scanner.ETokenType.ON; 027import static eu.cqse.check.framework.scanner.ETokenType.OVERFLOW; 028import static eu.cqse.check.framework.scanner.ETokenType.READ; 029import static eu.cqse.check.framework.scanner.ETokenType.RETURN; 030import static eu.cqse.check.framework.scanner.ETokenType.REWRITE; 031import static eu.cqse.check.framework.scanner.ETokenType.SIZE; 032import static eu.cqse.check.framework.scanner.ETokenType.START; 033import static eu.cqse.check.framework.scanner.ETokenType.STRING; 034import static eu.cqse.check.framework.scanner.ETokenType.SUBTRACT; 035import static eu.cqse.check.framework.scanner.ETokenType.UNSTRING; 036import static eu.cqse.check.framework.scanner.ETokenType.WAIT; 037import static eu.cqse.check.framework.scanner.ETokenType.WRITE; 038import static eu.cqse.check.framework.scanner.ETokenType.XML; 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.EnumSet; 043import java.util.List; 044 045import eu.cqse.check.framework.scanner.ETokenType; 046import eu.cqse.check.framework.scanner.IToken; 047import eu.cqse.check.framework.shallowparser.TokenStreamUtils; 048import eu.cqse.check.framework.shallowparser.framework.ParserState; 049import eu.cqse.check.framework.shallowparser.framework.RecognizerBase; 050 051/** 052 * This recognizer matches if the currently parsed verb is surrounded by 053 * different conditional clauses. 054 * 055 * Given a COBOL statement that should continue with a conditional clause. When 056 * parsing enters the clause, we might encounter another nested COBOL verb that 057 * also has that clause. For instance the CALL and DISPLAY verbs both have the 058 * (NOT) ON EXCEPTION clause. When both verbs occur in a statement like 059 * <code>CALL a ON EXCEPTION DISPLAY b NOT ON EXCEPTION DISPLAY c</code>, the 060 * parser needs to know which verb has which clause when it gets to the NOT 061 * term. For this example the <code>NOT ON EXCEPTION</code> belongs to the CALL 062 * verb. If there was not a <code>NOT</code> in the second clause, it would 063 * belong to the DISPLAY verb because by syntax verbs cannot usually have more 064 * than one occurrence of a clause in a statement. 065 */ 066public class BoundedByOppositeClausesRecognizer extends RecognizerBase<ECobolParserState> { 067 068 /** 069 * Cobol verbs with the (NOT) ON EXCEPTION conditional clause. 070 */ 071 private static final EnumSet<ETokenType> VERBS_WITH_ON_EXCEPTION_CLAUSE = EnumSet.of(CALL, DISPLAY, START, ACCEPT, 072 WAIT, XML); 073 074 /** 075 * Cobol verbs with the (NOT) ON ESCAPE conditional clause. 076 */ 077 private static final EnumSet<ETokenType> VERBS_WITH_ON_ESCAPE_CLAUSE = EnumSet.of(ACCEPT); 078 079 /** 080 * Cobol verbs with the (NOT) AT END conditional clause. 081 */ 082 private static final EnumSet<ETokenType> VERBS_WITH_AT_END_CLAUSE = EnumSet.of(READ, RETURN); 083 084 /** 085 * Cobol verbs with the (NOT) AT END_OF_PAGE conditional clause. 086 */ 087 private static final EnumSet<ETokenType> VERBS_WITH_AT_ENDOFPAGE_CLAUSE = EnumSet.of(WRITE); 088 089 /** 090 * Cobol verbs with the (NOT) AT EOP conditional clause. 091 */ 092 private static final EnumSet<ETokenType> VERBS_WITH_AT_EOP_CLAUSE = EnumSet.of(WRITE); 093 094 /** 095 * Cobol verbs with the (NOT) INVALID KEY conditional clause. 096 */ 097 private static final EnumSet<ETokenType> VERBS_WITH_INVALID_KEY_CLAUSE = EnumSet.of(READ, WRITE, START, DELETE, 098 REWRITE); 099 100 /** 101 * Cobol verbs with the (NOT) ON OVERFLOW conditional clause. 102 */ 103 private static final EnumSet<ETokenType> VERBS_WITH_ON_OVERFLOW_CLAUSE = EnumSet.of(STRING, UNSTRING, CALL); 104 105 /** 106 * Cobol verbs with the (NOT) ON SIZE ERROR conditional clause. 107 */ 108 private static final EnumSet<ETokenType> VERBS_WITH_ON_SIZE_ERROR_CLAUSE = EnumSet.of(COMPUTE, ADD, SUBTRACT, 109 MULTIPLY, DIVIDE); 110 111 /** 112 * Returns a list of conditional clauses for a given COBOL verb. 113 */ 114 private static List<ConditionalClause> getConditionalClauses(ETokenType verb) { 115 List<ConditionalClause> conditionalClauses = new ArrayList<>(); 116 117 updateConditionalClauses(conditionalClauses, VERBS_WITH_ON_EXCEPTION_CLAUSE, verb, Arrays.asList(EXCEPTION), ON, 118 true); 119 updateConditionalClauses(conditionalClauses, VERBS_WITH_ON_ESCAPE_CLAUSE, verb, Arrays.asList(ESCAPE), ON, 120 true); 121 updateConditionalClauses(conditionalClauses, VERBS_WITH_AT_END_CLAUSE, verb, Arrays.asList(END), AT, true); 122 updateConditionalClauses(conditionalClauses, VERBS_WITH_AT_ENDOFPAGE_CLAUSE, verb, Arrays.asList(END_OF_PAGE), 123 AT, true); 124 updateConditionalClauses(conditionalClauses, VERBS_WITH_AT_EOP_CLAUSE, verb, Arrays.asList(EOP), AT, true); 125 updateConditionalClauses(conditionalClauses, VERBS_WITH_INVALID_KEY_CLAUSE, verb, Arrays.asList(INVALID), KEY, 126 false); 127 updateConditionalClauses(conditionalClauses, VERBS_WITH_ON_OVERFLOW_CLAUSE, verb, Arrays.asList(OVERFLOW), ON, 128 true); 129 updateConditionalClauses(conditionalClauses, VERBS_WITH_ON_SIZE_ERROR_CLAUSE, verb, Arrays.asList(SIZE, ERROR), 130 ON, true); 131 132 return conditionalClauses; 133 } 134 135 /** 136 * Update the list of conditional clauses 137 */ 138 private static void updateConditionalClauses(List<ConditionalClause> clauses, EnumSet<ETokenType> clauseVerbs, 139 ETokenType verb, List<ETokenType> mandatoryTokens, ETokenType optionalToken, 140 boolean optionalTokenStartsClause) { 141 if (clauseVerbs.contains(verb)) { 142 clauses.add(new ConditionalClause(mandatoryTokens, optionalToken, optionalTokenStartsClause)); 143 } 144 } 145 146 /** 147 * Returns true if a given verb is surrounded by contradicting clauses 148 * otherwise false. 149 */ 150 private static boolean boundedByDifferentClauses(List<IToken> tokens, int verbIndex, int startOffset) { 151 List<ConditionalClause> clauses = getConditionalClauses(tokens.get(verbIndex).getType()); 152 for (ConditionalClause clause : clauses) { 153 ETokenType[] clauseWithOptionalToken = clause.getClauseTokens(true); 154 ETokenType[] clauseWithoutOptionalToken = clause.getClauseTokens(false); 155 ETokenType[] negatedClauseWithOptionalToken = clause.getNegatedClauseTokens(true); 156 ETokenType[] negatedClauseWithoutOptionalToken = clause.getNegatedClauseTokens(false); 157 if (boundedByClauses(clauseWithOptionalToken, negatedClauseWithOptionalToken, 158 negatedClauseWithoutOptionalToken, tokens, verbIndex, startOffset) 159 && tokens.get(verbIndex - clauseWithOptionalToken.length - 1).getType() != NOT) { 160 return true; 161 } 162 if (boundedByClauses(clauseWithoutOptionalToken, negatedClauseWithOptionalToken, 163 negatedClauseWithoutOptionalToken, tokens, verbIndex, startOffset) 164 && tokens.get(verbIndex - clauseWithoutOptionalToken.length - 1).getType() != ON 165 && tokens.get(verbIndex - clauseWithoutOptionalToken.length - 1).getType() != NOT) { 166 return true; 167 } 168 if (boundedByClauses(negatedClauseWithOptionalToken, clauseWithOptionalToken, clauseWithoutOptionalToken, 169 tokens, verbIndex, startOffset)) { 170 return true; 171 } 172 if (boundedByClauses(negatedClauseWithoutOptionalToken, clauseWithOptionalToken, clauseWithoutOptionalToken, 173 tokens, verbIndex, startOffset)) { 174 return true; 175 } 176 } 177 178 return false; 179 } 180 181 /** 182 * Given 3 clauses, returns true if given a verb is preceded by the first 183 * clause and succeeded by any of the other 2 clauses. 184 */ 185 private static boolean boundedByClauses(ETokenType[] startClauseTokens, ETokenType[] firstEndClauseTokens, 186 ETokenType[] secondEndClauseTokens, List<IToken> tokens, int verbIndex, int startOffset) { 187 if (!TokenStreamUtils.containsSequence(tokens, verbIndex - startClauseTokens.length, verbIndex, 188 startClauseTokens)) { 189 return false; 190 } 191 192 return TokenStreamUtils.containsSequence(tokens, startOffset, startOffset + firstEndClauseTokens.length, 193 firstEndClauseTokens) 194 || TokenStreamUtils.containsSequence(tokens, startOffset, startOffset + secondEndClauseTokens.length, 195 secondEndClauseTokens); 196 } 197 198 /** {@inheritDoc} */ 199 @Override 200 protected int matchesLocally(ParserState<ECobolParserState> parserState, List<IToken> tokens, int startOffset) { 201 int referencePosition = parserState.getCurrentReferencePosition(); 202 if (referencePosition - 3 < 0 || startOffset + 2 > tokens.size() - 1 || startOffset == tokens.size()) { 203 return NO_MATCH; 204 } 205 206 if (boundedByDifferentClauses(tokens, referencePosition, startOffset)) { 207 return NO_MATCH; 208 } 209 210 return super.matchesLocally(parserState, tokens, startOffset); 211 } 212 213}