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