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    }