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}