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