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.operation.InOperationTranslator;
027    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
028    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
029    import org.jetbrains.kotlin.psi.*;
030    import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
031    
032    import java.util.HashMap;
033    import java.util.Map;
034    
035    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated;
036    
037    public final class WhenTranslator extends AbstractTranslator {
038        @Nullable
039        public static JsNode translate(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
040            return new WhenTranslator(expression, context).translate();
041        }
042    
043        @NotNull
044        private final KtWhenExpression whenExpression;
045    
046        @Nullable
047        private final JsExpression expressionToMatch;
048    
049        private WhenTranslator(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
050            super(context);
051    
052            whenExpression = expression;
053    
054            KtExpression subject = expression.getSubjectExpression();
055            if (subject != null) {
056                JsExpression subjectExpression = Translation.translateAsExpression(subject, context);
057                if (TranslationUtils.isCacheNeeded(subjectExpression)) {
058                    TemporaryVariable subjectVar = context.declareTemporary(null);
059                    context.addStatementToCurrentBlock(JsAstUtils.assignment(subjectVar.reference(), subjectExpression).makeStmt());
060                    subjectExpression = subjectVar.reference();
061                }
062                expressionToMatch = subjectExpression;
063            }
064            else {
065                expressionToMatch = null;
066            }
067        }
068    
069        private JsStatement translate() {
070            if (expressionToMatch != null && JsAstUtils.isEmptyExpression(expressionToMatch)) {
071                return JsEmpty.INSTANCE;
072            }
073    
074            JsIf currentIf = null;
075            JsIf resultIf = null;
076            for (KtWhenEntry entry : whenExpression.getEntries()) {
077                JsBlock statementBlock = new JsBlock();
078                JsStatement statement = translateEntryExpression(entry, context(), statementBlock);
079    
080                if (resultIf == null && entry.isElse()) {
081                    context().addStatementsToCurrentBlockFrom(statementBlock);
082                    return statement;
083                }
084                statement = JsAstUtils.mergeStatementInBlockIfNeeded(statement, statementBlock);
085    
086                if (resultIf == null) {
087                    currentIf = JsAstUtils.newJsIf(translateConditions(entry, context()), statement);
088                    resultIf = currentIf;
089                }
090                else {
091                    if (entry.isElse()) {
092                        currentIf.setElseStatement(statement);
093                        return resultIf;
094                    }
095                    JsBlock conditionsBlock = new JsBlock();
096                    JsIf nextIf = JsAstUtils.newJsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement);
097                    JsStatement statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock);
098                    currentIf.setElseStatement(statementToAdd);
099                    currentIf = nextIf;
100                }
101            }
102            return resultIf;
103        }
104    
105        @NotNull
106        private static JsStatement translateEntryExpression(
107                @NotNull KtWhenEntry entry,
108                @NotNull TranslationContext context,
109                @NotNull JsBlock block) {
110            KtExpression expressionToExecute = entry.getExpression();
111            assert expressionToExecute != null : "WhenEntry should have whenExpression to execute.";
112            return Translation.translateAsStatement(expressionToExecute, context, block);
113        }
114    
115        @NotNull
116        private JsExpression translateConditions(@NotNull KtWhenEntry entry, @NotNull TranslationContext context) {
117            KtWhenCondition[] conditions = entry.getConditions();
118    
119            assert conditions.length > 0 : "When entry (not else) should have at least one condition";
120    
121            if (conditions.length == 1) {
122                return translateCondition(conditions[0], context);
123            }
124    
125            JsExpression result = translateCondition(conditions[0], context);
126            for (int i = 1; i < conditions.length; i++) {
127                result = translateOrCondition(result, conditions[i], context);
128            }
129    
130            return result;
131        }
132    
133        @NotNull
134        private JsExpression translateOrCondition(@NotNull JsExpression leftExpression, @NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
135            TranslationContext rightContext = context.innerBlock();
136            JsExpression rightExpression = translateCondition(condition, rightContext);
137            context.moveVarsFrom(rightContext);
138            if (rightContext.currentBlockIsEmpty()) {
139                return new JsBinaryOperation(JsBinaryOperator.OR, leftExpression, rightExpression);
140            } else {
141                assert rightExpression instanceof JsNameRef : "expected JsNameRef, but: " + rightExpression;
142                JsNameRef result = (JsNameRef) rightExpression;
143                JsIf ifStatement = JsAstUtils.newJsIf(leftExpression, JsAstUtils.assignment(result, JsLiteral.TRUE).makeStmt(),
144                                                      rightContext.getCurrentBlock());
145                context.addStatementToCurrentBlock(ifStatement);
146                return result;
147            }
148        }
149    
150        @NotNull
151        private JsExpression translateCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
152            JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context);
153            if (isNegated(condition)) {
154                return negated(patternMatchExpression);
155            }
156            return patternMatchExpression;
157        }
158    
159        @NotNull
160        private JsExpression translateWhenConditionToBooleanExpression(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
161            if (condition instanceof KtWhenConditionIsPattern) {
162                return translateIsCondition((KtWhenConditionIsPattern) condition, context);
163            }
164            else if (condition instanceof KtWhenConditionWithExpression) {
165                return translateExpressionCondition((KtWhenConditionWithExpression) condition, context);
166            }
167            else if (condition instanceof KtWhenConditionInRange) {
168                return translateRangeCondition((KtWhenConditionInRange) condition, context);
169            }
170            throw new AssertionError("Unsupported when condition " + condition.getClass());
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        @NotNull
199        private JsExpression translateRangeCondition(@NotNull KtWhenConditionInRange condition, @NotNull TranslationContext context) {
200            KtExpression patternExpression = condition.getRangeExpression();
201            assert patternExpression != null : "Expression pattern should have an expression: " +
202                                               PsiUtilsKt.getTextWithLocation(condition);
203    
204            JsExpression expressionToMatch = getExpressionToMatch();
205            assert expressionToMatch != null : "Range pattern is only available for 'when (C) { in ... }'  expressions: " +
206                                               PsiUtilsKt.getTextWithLocation(condition);
207    
208            Map<KtExpression, JsExpression> subjectAliases = new HashMap<KtExpression, JsExpression>();
209            subjectAliases.put(whenExpression.getSubjectExpression(), expressionToMatch);
210            TranslationContext callContext = context.innerContextWithAliasesForExpressions(subjectAliases);
211            return new InOperationTranslator(callContext, expressionToMatch, condition.getRangeExpression(), condition.getOperationReference())
212                    .translate();
213        }
214    
215        @Nullable
216        private JsExpression getExpressionToMatch() {
217            return expressionToMatch;
218        }
219    
220        private static boolean isNegated(@NotNull KtWhenCondition condition) {
221            if (condition instanceof KtWhenConditionIsPattern) {
222                return ((KtWhenConditionIsPattern)condition).isNegated();
223            }
224            return false;
225        }
226    }