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