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 }