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