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 JetWhenExpression expression, @NotNull TranslationContext context) {
035 return new WhenTranslator(expression, context).translate();
036 }
037
038 @NotNull
039 private final JetWhenExpression whenExpression;
040
041 @Nullable
042 private final JsExpression expressionToMatch;
043
044 private WhenTranslator(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) {
045 super(context);
046
047 whenExpression = expression;
048
049 JetExpression 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 (JetWhenEntry 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 JetWhenEntry entry,
103 @NotNull TranslationContext context,
104 @NotNull JsBlock block) {
105 JetExpression 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 JetWhenEntry entry, @NotNull TranslationContext context) {
112 JetWhenCondition[] 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 JetWhenCondition 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 JetWhenCondition condition, @NotNull TranslationContext context) {
147 if ((condition instanceof JetWhenConditionIsPattern) || (condition instanceof JetWhenConditionWithExpression)) {
148 return translatePatternCondition(condition, context);
149 }
150 throw new AssertionError("Unsupported when condition " + condition.getClass());
151 }
152
153 @NotNull
154 private JsExpression translatePatternCondition(@NotNull JetWhenCondition 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 JetWhenCondition condition, @NotNull TranslationContext context) {
164 if (condition instanceof JetWhenConditionIsPattern) {
165 return translateIsCondition((JetWhenConditionIsPattern) condition, context);
166 }
167 else if (condition instanceof JetWhenConditionWithExpression) {
168 return translateExpressionCondition((JetWhenConditionWithExpression) condition, context);
169 }
170 throw new AssertionError("Wrong type of JetWhenCondition");
171 }
172
173 @NotNull
174 private JsExpression translateIsCondition(@NotNull JetWhenConditionIsPattern conditionIsPattern, @NotNull TranslationContext context) {
175 JsExpression expressionToMatch = getExpressionToMatch();
176 assert expressionToMatch != null : "An is-check is not allowed in when() without subject.";
177
178 JetTypeReference 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 JetWhenConditionWithExpression condition, @NotNull TranslationContext context) {
186 JetExpression 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 JetWhenCondition condition) {
204 if (condition instanceof JetWhenConditionIsPattern) {
205 return ((JetWhenConditionIsPattern)condition).isNegated();
206 }
207 return false;
208 }
209 }