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
017package org.jetbrains.k2js.translate.expression;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import com.intellij.openapi.util.Pair;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.psi.*;
024import org.jetbrains.k2js.translate.context.TranslationContext;
025import org.jetbrains.k2js.translate.general.AbstractTranslator;
026import org.jetbrains.k2js.translate.general.Translation;
027import org.jetbrains.k2js.translate.utils.BindingUtils;
028import org.jetbrains.k2js.translate.utils.TranslationUtils;
029import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
030import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
031
032import java.util.List;
033
034import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
035import static org.jetbrains.k2js.translate.utils.JsAstUtils.negated;
036
037public 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}