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 }