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.JsExpression;
020import com.google.dart.compiler.backend.js.ast.JsInvocation;
021import com.google.dart.compiler.backend.js.ast.JsNameRef;
022import com.google.dart.compiler.backend.js.ast.JsNumberLiteral;
023import org.jetbrains.annotations.NotNull;
024import org.jetbrains.annotations.Nullable;
025import org.jetbrains.jet.lang.psi.*;
026import org.jetbrains.jet.lang.resolve.name.Name;
027import org.jetbrains.k2js.translate.context.TranslationContext;
028import org.jetbrains.k2js.translate.general.AbstractTranslator;
029import org.jetbrains.k2js.translate.general.Translation;
030
031import static org.jetbrains.k2js.translate.utils.JsAstUtils.sum;
032import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getNameIfStandardType;
033
034
035public final class StringTemplateTranslator extends AbstractTranslator {
036
037    @NotNull
038    public static JsExpression translate(@NotNull JetStringTemplateExpression expression,
039                                         @NotNull TranslationContext context) {
040        return (new StringTemplateTranslator(expression, context).translate());
041    }
042
043    @NotNull
044    private final JetStringTemplateExpression expression;
045
046    private StringTemplateTranslator(@NotNull JetStringTemplateExpression expression,
047                                     @NotNull TranslationContext context) {
048        super(context);
049        this.expression = expression;
050    }
051
052    @NotNull
053    private JsExpression translate() {
054        assert expression.getEntries().length != 0 : "String template must have one or more entries.";
055        EntryVisitor entryVisitor = new EntryVisitor();
056        for (JetStringTemplateEntry entry : expression.getEntries()) {
057            entry.accept(entryVisitor);
058        }
059        return entryVisitor.getResultingExpression();
060    }
061
062    private final class EntryVisitor extends JetVisitorVoid {
063
064        @Nullable
065        private JsExpression resultingExpression = null;
066
067        void append(@NotNull JsExpression expression) {
068            if (resultingExpression == null) {
069                resultingExpression = expression;
070            }
071            else {
072                resultingExpression = sum(resultingExpression, expression);
073            }
074        }
075
076        @Override
077        public void visitStringTemplateEntryWithExpression(@NotNull JetStringTemplateEntryWithExpression entry) {
078            JetExpression entryExpression = entry.getExpression();
079            assert entryExpression != null :
080                    "JetStringTemplateEntryWithExpression must have not null entry expression.";
081            JsExpression translatedExpression = Translation.translateAsExpression(entryExpression, context());
082            if (translatedExpression instanceof JsNumberLiteral) {
083                append(context().program().getStringLiteral(translatedExpression.toString()));
084                return;
085            }
086            if (mustCallToString(entryExpression)) {
087                append(new JsInvocation(new JsNameRef("toString", translatedExpression)));
088            } else {
089                append(translatedExpression);
090            }
091        }
092
093        private boolean mustCallToString(@NotNull JetExpression entryExpression) {
094            Name typeName = getNameIfStandardType(entryExpression, context());
095            if (typeName == null) {
096                return true;
097            }
098            //TODO: this is a hacky optimization, should use some generic approach
099            if (typeName.asString().equals("String")) {
100                return false;
101            }
102            if (typeName.asString().equals("Int") && resultingExpression != null) {
103                return false;
104            }
105            return true;
106        }
107
108        @Override
109        public void visitLiteralStringTemplateEntry(@NotNull JetLiteralStringTemplateEntry entry) {
110            appendText(entry.getText());
111        }
112
113        @Override
114        public void visitEscapeStringTemplateEntry(@NotNull JetEscapeStringTemplateEntry entry) {
115            appendText(entry.getUnescapedValue());
116        }
117
118        private void appendText(@NotNull String text) {
119            append(program().getStringLiteral(text));
120        }
121
122        @NotNull
123        public JsExpression getResultingExpression() {
124            assert resultingExpression != null;
125            return resultingExpression;
126        }
127    }
128}