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