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.builtins.KotlinBuiltIns;
023 import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
024 import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
025 import org.jetbrains.kotlin.idea.MainFunctionDetector;
026 import org.jetbrains.kotlin.js.config.JsConfig;
027 import org.jetbrains.kotlin.js.facade.MainCallParameters;
028 import org.jetbrains.kotlin.js.facade.exceptions.TranslationException;
029 import org.jetbrains.kotlin.js.facade.exceptions.TranslationRuntimeException;
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.BindingContext;
051 import org.jetbrains.kotlin.resolve.BindingTrace;
052 import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
053 import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
054 import org.jetbrains.kotlin.resolve.constants.ConstantValue;
055 import org.jetbrains.kotlin.resolve.constants.NullValue;
056 import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
057 import org.jetbrains.kotlin.types.KotlinType;
058 import org.jetbrains.kotlin.types.TypeUtils;
059 import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
060
061 import java.util.ArrayList;
062 import java.util.Collection;
063 import java.util.Collections;
064 import java.util.List;
065
066 import static org.jetbrains.kotlin.js.translate.general.ModuleWrapperTranslation.wrapIfNecessary;
067 import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor;
068 import static org.jetbrains.kotlin.js.translate.utils.ErrorReportingUtils.message;
069 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.convertToStatement;
070 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.toStringLiteralList;
071 import static org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
072
073 /**
074 * This class provides a interface which all translators use to interact with each other.
075 * Goal is to simplify interaction between translators.
076 */
077 public final class Translation {
078
079 private Translation() {
080 }
081
082 @NotNull
083 public static FunctionTranslator functionTranslator(@NotNull KtDeclarationWithBody function,
084 @NotNull TranslationContext context) {
085 return FunctionTranslator.newInstance(function, context);
086 }
087
088 @NotNull
089 public static PatternTranslator patternTranslator(@NotNull TranslationContext context) {
090 return PatternTranslator.newInstance(context);
091 }
092
093 @NotNull
094 public static JsNode translateExpression(@NotNull KtExpression expression, @NotNull TranslationContext context) {
095 return translateExpression(expression, context, context.dynamicContext().jsBlock());
096 }
097
098 @NotNull
099 public static JsNode translateExpression(@NotNull KtExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block) {
100 JsExpression aliasForExpression = context.aliasingContext().getAliasForExpression(expression);
101 if (aliasForExpression != null) {
102 return aliasForExpression;
103 }
104
105 TranslationContext innerContext = context.innerBlock();
106 JsNode result = doTranslateExpression(expression, innerContext);
107 context.moveVarsFrom(innerContext);
108 block.getStatements().addAll(innerContext.dynamicContext().jsBlock().getStatements());
109
110 return result;
111 }
112
113 @Nullable
114 public static JsExpression translateConstant(
115 @NotNull CompileTimeConstant compileTimeValue,
116 @NotNull KtExpression expression,
117 @NotNull TranslationContext context
118 ) {
119 KotlinType expectedType = context.bindingContext().getType(expression);
120 ConstantValue<?> constant = compileTimeValue.toConstantValue(expectedType != null ? expectedType : TypeUtils.NO_EXPECTED_TYPE);
121 if (constant instanceof NullValue) {
122 return JsLiteral.NULL;
123 }
124 Object value = constant.getValue();
125 if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
126 return context.program().getNumberLiteral(((Number) value).intValue());
127 }
128 else if (value instanceof Long) {
129 return JsAstUtils.newLong((Long) value, context);
130 }
131 else if (value instanceof Number) {
132 return context.program().getNumberLiteral(((Number) value).doubleValue());
133 }
134 else if (value instanceof Boolean) {
135 return JsLiteral.getBoolean((Boolean) value);
136 }
137
138 //TODO: test
139 if (value instanceof String) {
140 return context.program().getStringLiteral((String) value);
141 }
142 if (value instanceof Character) {
143 return context.program().getStringLiteral(value.toString());
144 }
145
146 return null;
147 }
148
149 @NotNull
150 private static JsNode doTranslateExpression(KtExpression expression, TranslationContext context) {
151 try {
152 return expression.accept(new ExpressionVisitor(), context);
153 }
154 catch (TranslationRuntimeException e) {
155 throw e;
156 }
157 catch (RuntimeException e) {
158 throw new TranslationRuntimeException(expression, e);
159 }
160 catch (AssertionError e) {
161 throw new TranslationRuntimeException(expression, e);
162 }
163 }
164
165 @NotNull
166 public static JsExpression translateAsExpression(@NotNull KtExpression expression, @NotNull TranslationContext context) {
167 return translateAsExpression(expression, context, context.dynamicContext().jsBlock());
168 }
169
170 @NotNull
171 public static JsExpression translateAsExpression(
172 @NotNull KtExpression expression,
173 @NotNull TranslationContext context,
174 @NotNull JsBlock block
175 ) {
176 CompileTimeConstant<?> compileTimeValue = ConstantExpressionEvaluator.getConstant(expression, context.bindingContext());
177 if (compileTimeValue != null) {
178 KotlinType type = context.bindingContext().getType(expression);
179 if (type != null && KotlinBuiltIns.isLong(type)) {
180 JsExpression constantResult = translateConstant(compileTimeValue, expression, context);
181 if (constantResult != null) return constantResult;
182 }
183 }
184
185 JsNode jsNode = translateExpression(expression, context, block);
186 if (jsNode instanceof JsExpression) {
187 return (JsExpression) jsNode;
188 }
189
190 assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString();
191 if (BindingContextUtilsKt.isUsedAsExpression(expression, context.bindingContext())) {
192 TemporaryVariable result = context.declareTemporary(null);
193 AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
194 block.getStatements().add(mutateLastExpression(jsNode, saveResultToTemporaryMutator));
195 return result.reference();
196 }
197
198 block.getStatements().add(convertToStatement(jsNode));
199 return JsLiteral.NULL;
200 }
201
202 @NotNull
203 public static JsStatement translateAsStatement(@NotNull KtExpression expression, @NotNull TranslationContext context) {
204 return translateAsStatement(expression, context, context.dynamicContext().jsBlock());
205 }
206
207 @NotNull
208 public static JsStatement translateAsStatement(
209 @NotNull KtExpression expression,
210 @NotNull TranslationContext context,
211 @NotNull JsBlock block) {
212 return convertToStatement(translateExpression(expression, context, block));
213 }
214
215 @NotNull
216 public static JsStatement translateAsStatementAndMergeInBlockIfNeeded(
217 @NotNull KtExpression expression,
218 @NotNull TranslationContext context
219 ) {
220 JsBlock block = new JsBlock();
221 JsNode node = translateExpression(expression, context, block);
222 return JsAstUtils.mergeStatementInBlockIfNeeded(convertToStatement(node), block);
223 }
224
225 @NotNull
226 public static TranslationContext generateAst(
227 @NotNull BindingTrace bindingTrace,
228 @NotNull Collection<KtFile> files,
229 @NotNull MainCallParameters mainCallParameters,
230 @NotNull ModuleDescriptor moduleDescriptor,
231 @NotNull JsConfig config
232 ) throws TranslationException {
233 try {
234 return doGenerateAst(bindingTrace, files, mainCallParameters, moduleDescriptor, config);
235 }
236 catch (UnsupportedOperationException e) {
237 throw new UnsupportedFeatureException("Unsupported feature used.", e);
238 }
239 catch (Throwable e) {
240 throw ExceptionUtilsKt.rethrow(e);
241 }
242 }
243
244 @NotNull
245 private static TranslationContext doGenerateAst(
246 @NotNull BindingTrace bindingTrace,
247 @NotNull Collection<KtFile> files,
248 @NotNull MainCallParameters mainCallParameters,
249 @NotNull ModuleDescriptor moduleDescriptor,
250 @NotNull JsConfig config
251 ) {
252 StaticContext staticContext = StaticContext.generateStaticContext(bindingTrace, config, moduleDescriptor);
253 JsProgram program = staticContext.getProgram();
254
255 JsFunction rootFunction = JsAstUtils.createFunctionWithEmptyBody(program.getScope());
256 JsBlock rootBlock = rootFunction.getBody();
257 List<JsStatement> statements = rootBlock.getStatements();
258 statements.add(program.getStringLiteral("use strict").makeStmt());
259
260 TranslationContext context = TranslationContext.rootContext(staticContext, rootFunction);
261 statements.addAll(PackageDeclarationTranslator.translateFiles(files, context));
262 defineModule(context, statements, config.getModuleId());
263
264 mayBeGenerateTests(files, config, rootBlock, context);
265
266 // Invoke function passing modules as arguments
267 // This should help minifier tool to recognize references to these modules as local variables and make them shorter.
268 List<String> importedModuleList = new ArrayList<String>();
269 JsName kotlinName = program.getScope().declareName(Namer.KOTLIN_NAME);
270 rootFunction.getParameters().add(new JsParameter((kotlinName)));
271 importedModuleList.add(Namer.KOTLIN_LOWER_NAME);
272
273 for (String importedModule : staticContext.getImportedModules().keySet()) {
274 rootFunction.getParameters().add(new JsParameter(staticContext.getImportedModules().get(importedModule)));
275 importedModuleList.add(importedModule);
276 }
277
278 if (mainCallParameters.shouldBeGenerated()) {
279 JsStatement statement = generateCallToMain(context, files, mainCallParameters.arguments());
280 if (statement != null) {
281 statements.add(statement);
282 }
283 }
284
285 statements.add(new JsReturn(program.getRootScope().declareName(Namer.getRootPackageName()).makeRef()));
286
287 JsBlock block = program.getGlobalBlock();
288 block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program,
289 config.getModuleKind()));
290
291 return context;
292 }
293
294 private static void defineModule(@NotNull TranslationContext context, @NotNull List<JsStatement> statements, @NotNull String moduleId) {
295 JsName rootPackageName = context.scope().findName(Namer.getRootPackageName());
296 if (rootPackageName != null) {
297 statements.add(new JsInvocation(context.namer().kotlin("defineModule"), context.program().getStringLiteral(moduleId),
298 rootPackageName.makeRef()).makeStmt());
299 }
300 }
301
302 private static void mayBeGenerateTests(
303 @NotNull Collection<KtFile> files, @NotNull JsConfig config, @NotNull JsBlock rootBlock, @NotNull TranslationContext context
304 ) {
305 JSTester tester = config.isTestConfig() ? new JSRhinoUnitTester() : new QUnitTester();
306 tester.initialize(context, rootBlock);
307 JSTestGenerator.generateTestCalls(context, files, tester);
308 tester.deinitialize();
309 }
310
311 //TODO: determine whether should throw exception
312 @Nullable
313 private static JsStatement generateCallToMain(
314 @NotNull TranslationContext context, @NotNull Collection<KtFile> files, @NotNull List<String> arguments
315 ) {
316 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(context.bindingContext());
317 KtNamedFunction mainFunction = mainFunctionDetector.getMainFunction(files);
318 if (mainFunction == null) {
319 return null;
320 }
321 FunctionDescriptor functionDescriptor = getFunctionDescriptor(context.bindingContext(), mainFunction);
322 JsArrayLiteral argument = new JsArrayLiteral(toStringLiteralList(arguments, context.program()));
323 return CallTranslator.INSTANCE.buildCall(context, functionDescriptor, Collections.singletonList(argument), null).makeStmt();
324 }
325 }