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    }