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 }