001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright (c) 2009-2020 CQSE GmbH                                        |
004|                                                                          |
005+-------------------------------------------------------------------------*/
006package eu.cqse.check.framework.shallowparser.languages.go;
007
008import static eu.cqse.check.framework.scanner.ETokenType.ADD;
009import static eu.cqse.check.framework.scanner.ETokenType.BACKSLASH;
010import static eu.cqse.check.framework.scanner.ETokenType.COLON;
011import static eu.cqse.check.framework.scanner.ETokenType.DOT;
012import static eu.cqse.check.framework.scanner.ETokenType.LBRACE;
013import static eu.cqse.check.framework.scanner.ETokenType.LBRACK;
014import static eu.cqse.check.framework.scanner.ETokenType.LPAREN;
015import static eu.cqse.check.framework.scanner.ETokenType.MINUSMINUS;
016import static eu.cqse.check.framework.scanner.ETokenType.NOT;
017import static eu.cqse.check.framework.scanner.ETokenType.PLUSPLUS;
018import static eu.cqse.check.framework.scanner.ETokenType.QUESTION;
019import static eu.cqse.check.framework.scanner.ETokenType.RBRACE;
020import static eu.cqse.check.framework.scanner.ETokenType.RBRACK;
021import static eu.cqse.check.framework.scanner.ETokenType.RPAREN;
022import static eu.cqse.check.framework.scanner.ETokenType.SEMICOLON;
023
024import java.util.EnumMap;
025import java.util.EnumSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Stack;
029
030import eu.cqse.check.framework.scanner.ETokenType;
031import eu.cqse.check.framework.scanner.IToken;
032import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
033import eu.cqse.check.framework.shallowparser.framework.ParserState;
034import eu.cqse.check.framework.shallowparser.framework.RecognizerBase;
035import eu.cqse.check.framework.shallowparser.languages.base.EGenericParserStates;
036
037/**
038 * Recognizer for the end of statements in Go. We need a separate recognizer as
039 * the rules for statement continuation are non-trivial due to the optional
040 * semicolon.
041 */
042public class GoSkipToEndOfStatementRecognizer extends RecognizerBase<EGenericParserStates> {
043
044        /** Token types that force a statement to continue even in a new line. */
045        private static final EnumSet<ETokenType> CONTINUE_TOKENS = EnumSet.of(BACKSLASH, DOT, QUESTION, COLON, NOT, ADD);
046
047        /** Token types of class OPERATOR that do not force a statement continuation. */
048        private static final EnumSet<ETokenType> NON_CONTINUATION_OPERATORS = EnumSet.of(MINUSMINUS, PLUSPLUS);
049
050        /** Matched tokens for nesting in complex Xtend statements. */
051        private static final Map<ETokenType, ETokenType> NESTING_MATCH = new EnumMap<>(ETokenType.class);
052
053        static {
054                NESTING_MATCH.put(LPAREN, RPAREN);
055                NESTING_MATCH.put(LBRACK, RBRACK);
056                NESTING_MATCH.put(LBRACE, RBRACE);
057        }
058
059        /**
060         * Defines if we should include the lastToken into the matching decision from
061         * the start on.
062         */
063        private boolean forceMatch = false;
064
065        /** @see #forceMatch */
066        public void setForceMatch(boolean forceMatch) {
067                this.forceMatch = forceMatch;
068        }
069
070        /** {@inheritDoc} */
071        @Override
072        protected int matchesLocally(ParserState<EGenericParserStates> parserState, List<IToken> tokens, int startOffset) {
073
074                if (startOffset < 0) {
075                        return NO_MATCH;
076                }
077                IToken lastToken = null;
078                if (!forceMatch && startOffset > 0) {
079                        lastToken = tokens.get(startOffset - 1);
080                }
081
082                Stack<ETokenType> expectedClosing = new Stack<>();
083
084                while (true) {
085                        if (startOffset >= tokens.size()) {
086                                return startOffset;
087                        }
088
089                        IToken token = tokens.get(startOffset);
090                        ETokenType tokenType = token.getType();
091
092                        if (!expectedClosing.isEmpty() && tokenType == expectedClosing.peek()) {
093                                expectedClosing.pop();
094                        } else if (expectedClosing.isEmpty() && tokenType == SEMICOLON
095                                        && tokens.get(0).getType() != ETokenType.FOR) {
096                                return startOffset + 1;
097                        } else if (expectedClosing.isEmpty() && TokenStreamUtils.startsNewStatement(token, lastToken,
098                                        CONTINUE_TOKENS, NON_CONTINUATION_OPERATORS, RBRACE)) {
099                                return startOffset;
100                        } else if (NESTING_MATCH.containsKey(tokenType)) {
101                                expectedClosing.push(NESTING_MATCH.get(tokenType));
102                        } else {
103                                startOffset++;
104                                lastToken = tokens.get(startOffset - 1);
105                                continue;
106                        }
107
108                        lastToken = token;
109                        startOffset += 1;
110                }
111        }
112}