eu.cqse.check.framework.shallowparser.languages.groovy.GroovySkipToEndOfStatementRecognizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-check-api Show documentation
Show all versions of teamscale-check-api Show documentation
The Teamscale Custom Check API allows users to extend Teamscale by writing custom analyses that create findings.
/*
* Copyright (c) CQSE GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.cqse.check.framework.shallowparser.languages.groovy;
import static eu.cqse.check.framework.scanner.ETokenType.BACKSLASH;
import static eu.cqse.check.framework.scanner.ETokenType.COLON;
import static eu.cqse.check.framework.scanner.ETokenType.DOT;
import static eu.cqse.check.framework.scanner.ETokenType.LBRACK;
import static eu.cqse.check.framework.scanner.ETokenType.LPAREN;
import static eu.cqse.check.framework.scanner.ETokenType.MINUSMINUS;
import static eu.cqse.check.framework.scanner.ETokenType.NOT;
import static eu.cqse.check.framework.scanner.ETokenType.PLUSPLUS;
import static eu.cqse.check.framework.scanner.ETokenType.QUESTION;
import static eu.cqse.check.framework.scanner.ETokenType.RBRACE;
import static eu.cqse.check.framework.scanner.ETokenType.RBRACK;
import static eu.cqse.check.framework.scanner.ETokenType.RPAREN;
import static eu.cqse.check.framework.scanner.ETokenType.SEMICOLON;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import eu.cqse.check.framework.scanner.ETokenType;
import eu.cqse.check.framework.scanner.IToken;
import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
import eu.cqse.check.framework.shallowparser.framework.ParserState;
import eu.cqse.check.framework.shallowparser.framework.RecognizerBase;
/**
* Recognizer for simple statements in Groovy. We need a separate recognizer as the rules for
* statement continuation are non-trivial due to the optional semicolon.
*/
/* package */ class GroovySkipToEndOfStatementRecognizer extends RecognizerBase {
/** Token types that force a statement to continue even in a new line. */
private static final EnumSet CONTINUE_TOKENS = EnumSet.of(BACKSLASH, DOT, QUESTION, COLON, NOT);
/**
* Token types of class OPERATOR that do not force a statement continuation.
*/
private static final EnumSet NON_CONTINUATION_OPERATORS = EnumSet.of(MINUSMINUS, PLUSPLUS);
/** Matched tokens for nesting in complex Xtend statements. */
private static final Map NESTING_MATCH = new EnumMap<>(ETokenType.class);
static {
NESTING_MATCH.put(LPAREN, RPAREN);
NESTING_MATCH.put(LBRACK, RBRACK);
}
/**
* Defines if we should include the lastToken into the matching decision from the start on.
*/
private boolean forceMatch = false;
/** @see #forceMatch */
public void setForceMatch(boolean forceMatch) {
this.forceMatch = forceMatch;
}
/** {@inheritDoc} */
@Override
protected int matchesLocally(ParserState parserState, List tokens,
int startOffset) {
if (startOffset < 0) {
return NO_MATCH;
}
IToken lastToken = null;
if (!forceMatch && startOffset > 0) {
lastToken = tokens.get(startOffset - 1);
}
Stack expectedClosing = new Stack<>();
while (true) {
if (startOffset >= tokens.size()) {
return startOffset;
}
IToken token = tokens.get(startOffset);
ETokenType tokenType = token.getType();
if (!expectedClosing.isEmpty() && tokenType == expectedClosing.peek()) {
expectedClosing.pop();
} else if (expectedClosing.isEmpty() && tokenType == SEMICOLON) {
return startOffset + 1;
} else if (expectedClosing.isEmpty() && TokenStreamUtils.startsNewStatement(token, lastToken,
CONTINUE_TOKENS, NON_CONTINUATION_OPERATORS, RBRACE)) {
return startOffset;
} else if (NESTING_MATCH.containsKey(tokenType)) {
expectedClosing.push(NESTING_MATCH.get(tokenType));
} else {
int next = startSubExpressionRecognizer(tokens, startOffset, parserState);
startOffset = next;
lastToken = tokens.get(startOffset - 1);
continue;
}
lastToken = token;
startOffset += 1;
}
}
/**
* Directly starts a subparse. Returns either the new offset from the subparsers or increments the
* offset, if no subparse was triggered.
*/
private static int startSubExpressionRecognizer(List tokens, int startOffset,
ParserState parserState) {
GroovySubExpressionRecognizer subExpressionRecognizer = new GroovySubExpressionRecognizer();
int next = subExpressionRecognizer.matches(parserState, tokens, startOffset);
if (next == NO_MATCH) {
return startOffset + 1;
}
return next;
}
}