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