001    /*
002     * Copyright 2010-2013 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.k2js.translate.expression;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.intellij.openapi.util.Pair;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.psi.*;
024    import org.jetbrains.k2js.translate.context.TranslationContext;
025    import org.jetbrains.k2js.translate.general.AbstractTranslator;
026    import org.jetbrains.k2js.translate.general.Translation;
027    import org.jetbrains.k2js.translate.utils.BindingUtils;
028    import org.jetbrains.k2js.translate.utils.TranslationUtils;
029    import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
030    import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
031    
032    import java.util.List;
033    
034    import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
035    import static org.jetbrains.k2js.translate.utils.JsAstUtils.negated;
036    
037    public final class WhenTranslator extends AbstractTranslator {
038        @Nullable
039        public static JsNode translate(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) {
040            WhenTranslator translator = new WhenTranslator(expression, context);
041    
042            if (BindingUtils.isStatement(context.bindingContext(), expression)) {
043                translator.translateAsStatement(context.dynamicContext().jsBlock().getStatements());
044                return null;
045            }
046    
047            return translator.translateAsExpression();
048        }
049    
050        @NotNull
051        private final JetWhenExpression whenExpression;
052    
053        @Nullable
054        private final Pair<JsVars.JsVar, JsExpression> expressionToMatch;
055    
056        @Nullable
057        private Pair<JsVars.JsVar, JsExpression> result;
058    
059        private WhenTranslator(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) {
060            super(context);
061    
062            whenExpression = expression;
063    
064            JetExpression subject = expression.getSubjectExpression();
065            if (subject != null) {
066                expressionToMatch = TranslationUtils.createTemporaryIfNeed(Translation.translateAsExpression(subject, context()), context);
067            }
068            else {
069                expressionToMatch = null;
070            }
071        }
072    
073        @Nullable
074        private JsNode translateAsExpression() {
075            result = context().dynamicContext().createTemporary(null);
076            translateAsStatement(context().dynamicContext().jsBlock().getStatements());
077            return result.second;
078        }
079    
080        private void translateAsStatement(List<JsStatement> statements) {
081            addTempVarsStatement(statements);
082    
083            JsIf prevIf = null;
084            for (JetWhenEntry entry : whenExpression.getEntries()) {
085                JsStatement statement = withReturnValueCaptured(translateEntryExpression(entry));
086                if (entry.isElse()) {
087                    if (prevIf == null) {
088                        statements.add(statement);
089                    }
090                    else {
091                        prevIf.setElseStatement(statement);
092                    }
093                    break;
094                }
095    
096                JsIf ifStatement = new JsIf(translateConditions(entry), statement);
097                if (prevIf == null) {
098                    statements.add(ifStatement);
099                }
100                else {
101                    prevIf.setElseStatement(ifStatement);
102                }
103                prevIf = ifStatement;
104            }
105        }
106    
107        private void addTempVarsStatement(List<JsStatement> statements) {
108            JsVars vars = new JsVars();
109            if (expressionToMatch != null && expressionToMatch.first != null) {
110                vars.add(expressionToMatch.first);
111            }
112            if (result != null) {
113                vars.add(result.first);
114            }
115    
116            if (!vars.isEmpty()) {
117                statements.add(vars);
118            }
119        }
120    
121        @NotNull
122        private JsStatement withReturnValueCaptured(@NotNull JsNode node) {
123            return result == null
124                   ? convertToStatement(node)
125                   : LastExpressionMutator.mutateLastExpression(node, new AssignToExpressionMutator(result.second));
126        }
127    
128        @NotNull
129        private JsNode translateEntryExpression(@NotNull JetWhenEntry entry) {
130            JetExpression expressionToExecute = entry.getExpression();
131            assert expressionToExecute != null : "WhenEntry should have whenExpression to execute.";
132            return Translation.translateExpression(expressionToExecute, context());
133        }
134    
135        @NotNull
136        private JsExpression translateConditions(@NotNull JetWhenEntry entry) {
137            JetWhenCondition[] conditions = entry.getConditions();
138    
139            assert conditions.length > 0 : "When entry (not else) should have at least one condition";
140    
141            if (conditions.length == 1) {
142                return translateCondition(conditions[0]);
143            }
144    
145            JsExpression result = translateCondition(conditions[0]);
146            for (int i = 1; i < conditions.length; i++) {
147                result = new JsBinaryOperation(JsBinaryOperator.OR, translateCondition(conditions[i]), result);
148            }
149    
150            return result;
151        }
152    
153        @NotNull
154        private JsExpression translateCondition(@NotNull JetWhenCondition condition) {
155            if ((condition instanceof JetWhenConditionIsPattern) || (condition instanceof JetWhenConditionWithExpression)) {
156                return translatePatternCondition(condition);
157            }
158            throw new AssertionError("Unsupported when condition " + condition.getClass());
159        }
160    
161        @NotNull
162        private JsExpression translatePatternCondition(@NotNull JetWhenCondition condition) {
163            JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition);
164            if (isNegated(condition)) {
165                return negated(patternMatchExpression);
166            }
167            return patternMatchExpression;
168        }
169    
170        @NotNull
171        private JsExpression translateWhenConditionToBooleanExpression(@NotNull JetWhenCondition condition) {
172            if (condition instanceof JetWhenConditionIsPattern) {
173                return translateIsCondition((JetWhenConditionIsPattern) condition);
174            }
175            else if (condition instanceof JetWhenConditionWithExpression) {
176                return translateExpressionCondition((JetWhenConditionWithExpression) condition);
177            }
178            throw new AssertionError("Wrong type of JetWhenCondition");
179        }
180    
181        @NotNull
182        private JsExpression translateIsCondition(@NotNull JetWhenConditionIsPattern conditionIsPattern) {
183            JsExpression expressionToMatch = getExpressionToMatch();
184            assert expressionToMatch != null : "An is-check is not allowed in when() without subject.";
185    
186            JetTypeReference typeReference = conditionIsPattern.getTypeRef();
187            assert typeReference != null : "An is-check must have a type reference.";
188    
189            return Translation.patternTranslator(context()).translateIsCheck(expressionToMatch, typeReference);
190        }
191    
192        @NotNull
193        private JsExpression translateExpressionCondition(@NotNull JetWhenConditionWithExpression condition) {
194            JetExpression patternExpression = condition.getExpression();
195            assert patternExpression != null : "Expression pattern should have an expression.";
196    
197            JsExpression expressionToMatch = getExpressionToMatch();
198            if (expressionToMatch == null) {
199                return Translation.patternTranslator(context()).translateExpressionForExpressionPattern(patternExpression);
200            }
201            else {
202                return Translation.patternTranslator(context()).translateExpressionPattern(expressionToMatch, patternExpression);
203            }
204        }
205    
206        @Nullable
207        private JsExpression getExpressionToMatch() {
208            return expressionToMatch != null ? expressionToMatch.second : null;
209        }
210    
211        private static boolean isNegated(@NotNull JetWhenCondition condition) {
212            if (condition instanceof JetWhenConditionIsPattern) {
213                return ((JetWhenConditionIsPattern)condition).isNegated();
214            }
215            return false;
216        }
217    }