001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.js.translate.expression;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
023    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
024    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
025    import org.jetbrains.kotlin.js.translate.general.Translation;
026    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
027    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
028    import org.jetbrains.kotlin.psi.*;
029    
030    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated;
031    
032    public final class WhenTranslator extends AbstractTranslator {
033        @Nullable
034        public static JsNode translate(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
035            return new WhenTranslator(expression, context).translate();
036        }
037    
038        @NotNull
039        private final KtWhenExpression whenExpression;
040    
041        @Nullable
042        private final JsExpression expressionToMatch;
043    
044        private WhenTranslator(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
045            super(context);
046    
047            whenExpression = expression;
048    
049            KtExpression subject = expression.getSubjectExpression();
050            if (subject != null) {
051                JsExpression subjectExpression = Translation.translateAsExpression(subject, context);
052                if (TranslationUtils.isCacheNeeded(subjectExpression)) {
053                    TemporaryVariable subjectVar = context.declareTemporary(null);
054                    context.addStatementToCurrentBlock(JsAstUtils.assignment(subjectVar.reference(), subjectExpression).makeStmt());
055                    subjectExpression = subjectVar.reference();
056                }
057                expressionToMatch = subjectExpression;
058            }
059            else {
060                expressionToMatch = null;
061            }
062        }
063    
064        private JsStatement translate() {
065            if (expressionToMatch != null && JsAstUtils.isEmptyExpression(expressionToMatch)) {
066                return JsEmpty.INSTANCE$;
067            }
068    
069            JsIf currentIf = null;
070            JsIf resultIf = null;
071            for (KtWhenEntry entry : whenExpression.getEntries()) {
072                JsBlock statementBlock = new JsBlock();
073                JsStatement statement = translateEntryExpression(entry, context(), statementBlock);
074    
075                if (resultIf == null && entry.isElse()) {
076                    context().addStatementsToCurrentBlockFrom(statementBlock);
077                    return statement;
078                }
079                statement = JsAstUtils.mergeStatementInBlockIfNeeded(statement, statementBlock);
080    
081                if (resultIf == null) {
082                    currentIf = JsAstUtils.newJsIf(translateConditions(entry, context()), statement);
083                    resultIf = currentIf;
084                }
085                else {
086                    if (entry.isElse()) {
087                        currentIf.setElseStatement(statement);
088                        return resultIf;
089                    }
090                    JsBlock conditionsBlock = new JsBlock();
091                    JsIf nextIf = JsAstUtils.newJsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement);
092                    JsStatement statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock);
093                    currentIf.setElseStatement(statementToAdd);
094                    currentIf = nextIf;
095                }
096            }
097            return resultIf;
098        }
099    
100        @NotNull
101        private static JsStatement translateEntryExpression(
102                @NotNull KtWhenEntry entry,
103                @NotNull TranslationContext context,
104                @NotNull JsBlock block) {
105            KtExpression expressionToExecute = entry.getExpression();
106            assert expressionToExecute != null : "WhenEntry should have whenExpression to execute.";
107            return Translation.translateAsStatement(expressionToExecute, context, block);
108        }
109    
110        @NotNull
111        private JsExpression translateConditions(@NotNull KtWhenEntry entry, @NotNull TranslationContext context) {
112            KtWhenCondition[] conditions = entry.getConditions();
113    
114            assert conditions.length > 0 : "When entry (not else) should have at least one condition";
115    
116            if (conditions.length == 1) {
117                return translateCondition(conditions[0], context);
118            }
119    
120            JsExpression result = translateCondition(conditions[0], context);
121            for (int i = 1; i < conditions.length; i++) {
122                result = translateOrCondition(result, conditions[i], context);
123            }
124    
125            return result;
126        }
127    
128        @NotNull
129        private JsExpression translateOrCondition(@NotNull JsExpression leftExpression, @NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
130            TranslationContext rightContext = context.innerBlock();
131            JsExpression rightExpression = translateCondition(condition, rightContext);
132            context.moveVarsFrom(rightContext);
133            if (rightContext.currentBlockIsEmpty()) {
134                return new JsBinaryOperation(JsBinaryOperator.OR, leftExpression, rightExpression);
135            } else {
136                assert rightExpression instanceof JsNameRef : "expected JsNameRef, but: " + rightExpression;
137                JsNameRef result = (JsNameRef) rightExpression;
138                JsIf ifStatement = JsAstUtils.newJsIf(leftExpression, JsAstUtils.assignment(result, JsLiteral.TRUE).makeStmt(),
139                                                      rightContext.getCurrentBlock());
140                context.addStatementToCurrentBlock(ifStatement);
141                return result;
142            }
143        }
144    
145        @NotNull
146        private JsExpression translateCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
147            if ((condition instanceof KtWhenConditionIsPattern) || (condition instanceof KtWhenConditionWithExpression)) {
148                return translatePatternCondition(condition, context);
149            }
150            throw new AssertionError("Unsupported when condition " + condition.getClass());
151        }
152    
153        @NotNull
154        private JsExpression translatePatternCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
155            JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context);
156            if (isNegated(condition)) {
157                return negated(patternMatchExpression);
158            }
159            return patternMatchExpression;
160        }
161    
162        @NotNull
163        private JsExpression translateWhenConditionToBooleanExpression(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
164            if (condition instanceof KtWhenConditionIsPattern) {
165                return translateIsCondition((KtWhenConditionIsPattern) condition, context);
166            }
167            else if (condition instanceof KtWhenConditionWithExpression) {
168                return translateExpressionCondition((KtWhenConditionWithExpression) condition, context);
169            }
170            throw new AssertionError("Wrong type of JetWhenCondition");
171        }
172    
173        @NotNull
174        private JsExpression translateIsCondition(@NotNull KtWhenConditionIsPattern conditionIsPattern, @NotNull TranslationContext context) {
175            JsExpression expressionToMatch = getExpressionToMatch();
176            assert expressionToMatch != null : "An is-check is not allowed in when() without subject.";
177    
178            KtTypeReference typeReference = conditionIsPattern.getTypeReference();
179            assert typeReference != null : "An is-check must have a type reference.";
180    
181            return Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference);
182        }
183    
184        @NotNull
185        private JsExpression translateExpressionCondition(@NotNull KtWhenConditionWithExpression condition, @NotNull TranslationContext context) {
186            KtExpression patternExpression = condition.getExpression();
187            assert patternExpression != null : "Expression pattern should have an expression.";
188    
189            JsExpression expressionToMatch = getExpressionToMatch();
190            if (expressionToMatch == null) {
191                return Translation.patternTranslator(context).translateExpressionForExpressionPattern(patternExpression);
192            }
193            else {
194                return Translation.patternTranslator(context).translateExpressionPattern(expressionToMatch, patternExpression);
195            }
196        }
197    
198        @Nullable
199        private JsExpression getExpressionToMatch() {
200            return expressionToMatch;
201        }
202    
203        private static boolean isNegated(@NotNull KtWhenCondition condition) {
204            if (condition instanceof KtWhenConditionIsPattern) {
205                return ((KtWhenConditionIsPattern)condition).isNegated();
206            }
207            return false;
208        }
209    }