001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package eu.cqse.check.framework.shallowparser.languages.ruby;
018
019import static eu.cqse.check.framework.scanner.ETokenType.BEGIN;
020import static eu.cqse.check.framework.scanner.ETokenType.CLASS;
021import static eu.cqse.check.framework.scanner.ETokenType.DEF;
022import static eu.cqse.check.framework.scanner.ETokenType.DO;
023import static eu.cqse.check.framework.scanner.ETokenType.DOT;
024import static eu.cqse.check.framework.scanner.ETokenType.ELSE;
025import static eu.cqse.check.framework.scanner.ETokenType.ELSIF;
026import static eu.cqse.check.framework.scanner.ETokenType.END;
027import static eu.cqse.check.framework.scanner.ETokenType.ENSURE;
028import static eu.cqse.check.framework.scanner.ETokenType.FOR;
029import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER;
030import static eu.cqse.check.framework.scanner.ETokenType.IF;
031import static eu.cqse.check.framework.scanner.ETokenType.INTERPOLATIONEND;
032import static eu.cqse.check.framework.scanner.ETokenType.INTERPOLATIONSTART;
033import static eu.cqse.check.framework.scanner.ETokenType.LBRACE;
034import static eu.cqse.check.framework.scanner.ETokenType.LPAREN;
035import static eu.cqse.check.framework.scanner.ETokenType.LSHIFT;
036import static eu.cqse.check.framework.scanner.ETokenType.MODULE;
037import static eu.cqse.check.framework.scanner.ETokenType.RBRACE;
038import static eu.cqse.check.framework.scanner.ETokenType.RESCUE;
039import static eu.cqse.check.framework.scanner.ETokenType.RPAREN;
040import static eu.cqse.check.framework.scanner.ETokenType.SELF;
041import static eu.cqse.check.framework.scanner.ETokenType.SEMICOLON;
042import static eu.cqse.check.framework.scanner.ETokenType.STRING_LITERAL;
043import static eu.cqse.check.framework.scanner.ETokenType.UNLESS;
044import static eu.cqse.check.framework.scanner.ETokenType.UNTIL;
045import static eu.cqse.check.framework.scanner.ETokenType.WHILE;
046import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.METHOD;
047import static eu.cqse.check.framework.shallowparser.languages.ruby.RubyShallowParser.ERubyParserStates.ANY;
048
049import java.util.EnumSet;
050
051import org.conqat.lib.commons.region.Region;
052
053import eu.cqse.check.framework.shallowparser.SubTypeNames;
054import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
055import eu.cqse.check.framework.shallowparser.framework.RecognizerBase;
056import eu.cqse.check.framework.shallowparser.framework.ShallowParserBase;
057import eu.cqse.check.framework.shallowparser.languages.ruby.RubyShallowParser.ERubyParserStates;
058
059/**
060 * Shallow parser for JavaScript. The parser is aware of Google closure and
061 * supports special handling of the provide, require and inherits statements.
062 */
063public class RubyShallowParser extends ShallowParserBase<ERubyParserStates> {
064
065        /** The states used in this parser. */
066        public enum ERubyParserStates {
067
068                /** Single state, as any construct can occur at any place. */
069                ANY
070        }
071
072        /** Constructor. */
073        public RubyShallowParser() {
074                super(ERubyParserStates.class, ERubyParserStates.ANY);
075
076                createStringLiteralRules();
077                createClassRules();
078                createFunctionRules();
079                createStatementRules();
080        }
081
082        /**
083         * Stitches together string literals with interpolations.
084         */
085        private void createStringLiteralRules() {
086                inState(ANY).sequence(STRING_LITERAL).skipNested(INTERPOLATIONSTART, INTERPOLATIONEND).optional(STRING_LITERAL);
087        }
088
089        /** Creates parsing rules for classes. */
090        private void createClassRules() {
091                // class << self
092                inState(ANY).sequence(CLASS, LSHIFT, SELF).optional(SEMICOLON)
093                                .createNode(EShallowEntityType.META, "static declarations").parseUntil(ANY).sequence(END).endNode();
094                // class
095                inState(ANY).sequence(CLASS, IDENTIFIER).optional(SEMICOLON).createNode(EShallowEntityType.TYPE, "class", 1)
096                                .parseUntil(ANY).sequence(END).endNode();
097                // module
098                inState(ANY).sequence(MODULE, IDENTIFIER).optional(SEMICOLON).createNode(EShallowEntityType.TYPE, "module", 1)
099                                .parseUntil(ANY).sequence(END).endNode();
100        }
101
102        /** Creates parsing rules for functions. */
103        private void createFunctionRules() {
104                // named function/method
105                inState(ANY).sequence(DEF, IDENTIFIER).optional(SEMICOLON).createNode(EShallowEntityType.METHOD, "method", 1)
106                                .skipNested(LPAREN, RPAREN).parseUntil(ANY).sequence(END).endNode();
107                // static function/method
108                inState(ANY).sequence(DEF, SELF, DOT, IDENTIFIER).optional(SEMICOLON)
109                                .createNode(EShallowEntityType.METHOD, "method", new Region(1, 3)).skipNested(LPAREN, RPAREN)
110                                .parseUntil(ANY).sequence(END).endNode();
111        }
112
113        /** Creates parsing rules for statements. */
114        private void createStatementRules() {
115                // empty statement
116                inState(ANY).sequence(SEMICOLON).createNode(EShallowEntityType.STATEMENT, SubTypeNames.EMPTY_STATEMENT)
117                                .endNode();
118
119                // blocks
120                inState(ANY).sequence(DO).createNode(METHOD, "block").parseUntil(ANY).sequence(END).endNode();
121                inState(ANY).sequence(LBRACE).createNode(METHOD, "block").parseUntil(ANY).sequence(RBRACE).endNode();
122
123                // if statements
124                RecognizerBase<ERubyParserStates> ifAlternative = inState(ANY).sequence(EnumSet.of(IF, ELSIF, UNLESS))
125                                .createNode(EShallowEntityType.STATEMENT, 0).subRecognizer(new RubyBlockStartRecognizer(), 1, 1)
126                                .parseUntil(ANY);
127                appendTrailingConditionRecognizer(ifAlternative.sequence(END)).endNode();
128                ifAlternative.sequenceBefore(EnumSet.of(ELSE, ELSIF)).endNodeWithContinuation();
129                appendTrailingConditionRecognizer(inState(ANY).sequence(ELSE)
130                                .createNode(EShallowEntityType.STATEMENT, SubTypeNames.ELSE).parseUntil(ANY).sequence(END)).endNode();
131
132                // while/for/until statements
133                appendTrailingConditionRecognizer(
134                                inState(ANY).sequence(EnumSet.of(WHILE, UNTIL, FOR)).createNode(EShallowEntityType.STATEMENT, 0)
135                                                .subRecognizer(new RubyBlockStartRecognizer(), 1, 1).parseUntil(ANY).sequence(END)).endNode();
136
137                // begin/end statements
138                RecognizerBase<ERubyParserStates> beginAlternative = inState(ANY).sequence(EnumSet.of(BEGIN))
139                                .createNode(EShallowEntityType.STATEMENT, 0).parseUntil(ANY);
140
141                appendTrailingConditionRecognizer(beginAlternative.sequence(END)).endNode();
142                beginAlternative.sequenceBefore(EnumSet.of(RESCUE, ENSURE)).endNodeWithContinuation();
143
144                appendTrailingConditionRecognizer(
145                                inState(ANY).sequence(ENSURE).createNode(EShallowEntityType.STATEMENT, 0).parseUntil(ANY).sequence(END))
146                                                .endNode();
147
148                RecognizerBase<ERubyParserStates> rescueAlternative = inState(ANY).sequenceBefore(RESCUE)
149                                .createNode(EShallowEntityType.STATEMENT, 0).subRecognizer(new RubyBlockStartRecognizer(), 1, 1)
150                                .parseUntil(ANY);
151
152                appendTrailingConditionRecognizer(rescueAlternative.sequence(END)).endNode();
153                rescueAlternative.sequenceBefore(EnumSet.of(RESCUE, ENSURE)).endNodeWithContinuation();
154
155                // TODO case/when statements
156
157                // simple statement
158                inState(ANY).createNode(EShallowEntityType.STATEMENT, SubTypeNames.SIMPLE_STATEMENT, 0)
159                                .subRecognizer(new RubySimpleStatementRecognizer(), 1, 1).endNode();
160        }
161
162        private RecognizerBase<ERubyParserStates> appendTrailingConditionRecognizer(
163                        RecognizerBase<ERubyParserStates> recognizer) {
164                return recognizer
165                                .subRecognizer(createRecognizer(start -> start.sequence(EnumSet.of(FOR, IF, UNLESS, WHILE, UNTIL)))
166                                                .subRecognizer(new RubyTrailingConditionRecognizer(), 1, 1), 0, Integer.MAX_VALUE);
167        }
168}