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.general;
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.descriptors.FunctionDescriptor;
023    import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
024    import org.jetbrains.kotlin.idea.MainFunctionDetector;
025    import org.jetbrains.kotlin.js.config.Config;
026    import org.jetbrains.kotlin.js.facade.MainCallParameters;
027    import org.jetbrains.kotlin.js.facade.exceptions.MainFunctionNotFoundException;
028    import org.jetbrains.kotlin.js.facade.exceptions.TranslationException;
029    import org.jetbrains.kotlin.js.facade.exceptions.TranslationInternalException;
030    import org.jetbrains.kotlin.js.facade.exceptions.UnsupportedFeatureException;
031    import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
032    import org.jetbrains.kotlin.js.translate.context.Namer;
033    import org.jetbrains.kotlin.js.translate.context.StaticContext;
034    import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
035    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
036    import org.jetbrains.kotlin.js.translate.declaration.PackageDeclarationTranslator;
037    import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor;
038    import org.jetbrains.kotlin.js.translate.expression.FunctionTranslator;
039    import org.jetbrains.kotlin.js.translate.expression.PatternTranslator;
040    import org.jetbrains.kotlin.js.translate.test.JSRhinoUnitTester;
041    import org.jetbrains.kotlin.js.translate.test.JSTestGenerator;
042    import org.jetbrains.kotlin.js.translate.test.JSTester;
043    import org.jetbrains.kotlin.js.translate.test.QUnitTester;
044    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
045    import org.jetbrains.kotlin.js.translate.utils.mutator.AssignToExpressionMutator;
046    import org.jetbrains.kotlin.psi.KtDeclarationWithBody;
047    import org.jetbrains.kotlin.psi.KtExpression;
048    import org.jetbrains.kotlin.psi.KtFile;
049    import org.jetbrains.kotlin.psi.KtNamedFunction;
050    import org.jetbrains.kotlin.resolve.BindingTrace;
051    import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
052    
053    import java.util.Collection;
054    import java.util.Collections;
055    import java.util.List;
056    
057    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor;
058    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.convertToStatement;
059    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.toStringLiteralList;
060    import static org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
061    
062    /**
063     * This class provides a interface which all translators use to interact with each other.
064     * Goal is to simplify interaction between translators.
065     */
066    public final class Translation {
067    
068        private Translation() {
069        }
070    
071        @NotNull
072        public static FunctionTranslator functionTranslator(@NotNull KtDeclarationWithBody function,
073                @NotNull TranslationContext context) {
074            return FunctionTranslator.newInstance(function, context);
075        }
076    
077        @NotNull
078        public static PatternTranslator patternTranslator(@NotNull TranslationContext context) {
079            return PatternTranslator.newInstance(context);
080        }
081    
082        @NotNull
083        public static JsNode translateExpression(@NotNull KtExpression expression, @NotNull TranslationContext context) {
084            return translateExpression(expression, context, context.dynamicContext().jsBlock());
085        }
086    
087        @NotNull
088        public static JsNode translateExpression(@NotNull KtExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block) {
089            JsExpression aliasForExpression = context.aliasingContext().getAliasForExpression(expression);
090            if (aliasForExpression != null) {
091                return aliasForExpression;
092            }
093    
094            TranslationContext innerContext = context.innerBlock();
095            JsNode result = doTranslateExpression(expression, innerContext);
096            context.moveVarsFrom(innerContext);
097            block.getStatements().addAll(innerContext.dynamicContext().jsBlock().getStatements());
098    
099            if (BindingContextUtilsKt.isUnreachableCode(expression, context.bindingContext())) {
100                return context.getEmptyExpression();
101            }
102    
103            return result;
104        }
105    
106        //NOTE: use with care
107        @NotNull
108        public static JsNode doTranslateExpression(KtExpression expression, TranslationContext context) {
109            return expression.accept(new ExpressionVisitor(), context);
110        }
111    
112        @NotNull
113        public static JsExpression translateAsExpression(@NotNull KtExpression expression, @NotNull TranslationContext context) {
114            return translateAsExpression(expression, context, context.dynamicContext().jsBlock());
115        }
116    
117        @NotNull
118        public static JsExpression translateAsExpression(
119                @NotNull KtExpression expression,
120                @NotNull TranslationContext context,
121                @NotNull JsBlock block
122        ) {
123            JsNode jsNode = translateExpression(expression, context, block);
124            if (jsNode instanceof  JsExpression) {
125                return (JsExpression) jsNode;
126            }
127    
128            assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString();
129            if (BindingContextUtilsKt.isUsedAsExpression(expression, context.bindingContext())) {
130                TemporaryVariable result = context.declareTemporary(null);
131                AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
132                block.getStatements().add(mutateLastExpression(jsNode, saveResultToTemporaryMutator));
133                return result.reference();
134            }
135    
136            block.getStatements().add(convertToStatement(jsNode));
137            return context.getEmptyExpression();
138        }
139    
140        @NotNull
141        public static JsStatement translateAsStatement(@NotNull KtExpression expression, @NotNull TranslationContext context) {
142            return translateAsStatement(expression, context, context.dynamicContext().jsBlock());
143        }
144    
145        @NotNull
146        public static JsStatement translateAsStatement(
147                @NotNull KtExpression expression,
148                @NotNull TranslationContext context,
149                @NotNull JsBlock block) {
150            return convertToStatement(translateExpression(expression, context, block));
151        }
152    
153        @NotNull
154        public static JsStatement translateAsStatementAndMergeInBlockIfNeeded(
155                @NotNull KtExpression expression,
156                @NotNull TranslationContext context
157        ) {
158            JsBlock block = new JsBlock();
159            JsNode node = translateExpression(expression, context, block);
160            return JsAstUtils.mergeStatementInBlockIfNeeded(convertToStatement(node), block);
161        }
162    
163        @NotNull
164        public static TranslationContext generateAst(@NotNull BindingTrace bindingTrace,
165                @NotNull Collection<KtFile> files, @NotNull MainCallParameters mainCallParameters,
166                @NotNull ModuleDescriptor moduleDescriptor,
167                @NotNull Config config)
168                throws TranslationException {
169            try {
170                return doGenerateAst(bindingTrace, files, mainCallParameters, moduleDescriptor, config);
171            }
172            catch (UnsupportedOperationException e) {
173                throw new UnsupportedFeatureException("Unsupported feature used.", e);
174            }
175            catch (Throwable e) {
176                throw new TranslationInternalException(e);
177            }
178        }
179    
180        @NotNull
181        private static TranslationContext doGenerateAst(@NotNull BindingTrace bindingTrace, @NotNull Collection<KtFile> files,
182                @NotNull MainCallParameters mainCallParameters,
183                @NotNull ModuleDescriptor moduleDescriptor,
184                @NotNull Config config) throws MainFunctionNotFoundException {
185            StaticContext staticContext = StaticContext.generateStaticContext(bindingTrace, config, moduleDescriptor);
186            JsProgram program = staticContext.getProgram();
187            JsBlock block = program.getGlobalBlock();
188    
189            JsFunction rootFunction = JsAstUtils.createPackage(block.getStatements(), program.getScope());
190            JsBlock rootBlock = rootFunction.getBody();
191            List<JsStatement> statements = rootBlock.getStatements();
192            statements.add(program.getStringLiteral("use strict").makeStmt());
193    
194            TranslationContext context = TranslationContext.rootContext(staticContext, rootFunction);
195            statements.addAll(PackageDeclarationTranslator.translateFiles(files, context));
196            defineModule(context, statements, config.getModuleId());
197    
198            if (mainCallParameters.shouldBeGenerated()) {
199                JsStatement statement = generateCallToMain(context, files, mainCallParameters.arguments());
200                if (statement != null) {
201                    statements.add(statement);
202                }
203            }
204            mayBeGenerateTests(files, config, rootBlock, context);
205            return context;
206        }
207    
208        private static void defineModule(@NotNull TranslationContext context, @NotNull List<JsStatement> statements, @NotNull String moduleId) {
209            JsName rootPackageName = context.scope().findName(Namer.getRootPackageName());
210            if (rootPackageName != null) {
211                statements.add(new JsInvocation(context.namer().kotlin("defineModule"), context.program().getStringLiteral(moduleId),
212                                                rootPackageName.makeRef()).makeStmt());
213            }
214        }
215    
216        private static void mayBeGenerateTests(@NotNull Collection<KtFile> files, @NotNull Config config,
217                @NotNull JsBlock rootBlock, @NotNull TranslationContext context) {
218            JSTester tester = config.isTestConfig() ? new JSRhinoUnitTester() : new QUnitTester();
219            tester.initialize(context, rootBlock);
220            JSTestGenerator.generateTestCalls(context, files, tester);
221            tester.deinitialize();
222        }
223    
224        //TODO: determine whether should throw exception
225        @Nullable
226        private static JsStatement generateCallToMain(@NotNull TranslationContext context, @NotNull Collection<KtFile> files,
227                @NotNull List<String> arguments) throws MainFunctionNotFoundException {
228            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(context.bindingContext());
229            KtNamedFunction mainFunction = mainFunctionDetector.getMainFunction(files);
230            if (mainFunction == null) {
231                return null;
232            }
233            FunctionDescriptor functionDescriptor = getFunctionDescriptor(context.bindingContext(), mainFunction);
234            JsArrayLiteral argument = new JsArrayLiteral(toStringLiteralList(arguments, context.program()));
235            return CallTranslator.INSTANCE.buildCall(context, functionDescriptor, Collections.singletonList(argument), null).makeStmt();
236        }
237    }