001    /*
002     * Copyright 2010-2013 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.k2js.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.jet.lang.descriptors.FunctionDescriptor;
023    import org.jetbrains.jet.lang.descriptors.ModuleDescriptor;
024    import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
025    import org.jetbrains.jet.lang.psi.JetExpression;
026    import org.jetbrains.jet.lang.psi.JetFile;
027    import org.jetbrains.jet.lang.psi.JetNamedFunction;
028    import org.jetbrains.jet.lang.resolve.BindingContext;
029    import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
030    import org.jetbrains.jet.plugin.MainFunctionDetector;
031    import org.jetbrains.k2js.config.Config;
032    import org.jetbrains.k2js.facade.MainCallParameters;
033    import org.jetbrains.k2js.facade.exceptions.MainFunctionNotFoundException;
034    import org.jetbrains.k2js.facade.exceptions.TranslationException;
035    import org.jetbrains.k2js.facade.exceptions.TranslationInternalException;
036    import org.jetbrains.k2js.facade.exceptions.UnsupportedFeatureException;
037    import org.jetbrains.k2js.inline.JsInliner;
038    import org.jetbrains.k2js.translate.callTranslator.CallTranslator;
039    import org.jetbrains.k2js.translate.context.Namer;
040    import org.jetbrains.k2js.translate.context.StaticContext;
041    import org.jetbrains.k2js.translate.context.TemporaryVariable;
042    import org.jetbrains.k2js.translate.context.TranslationContext;
043    import org.jetbrains.k2js.translate.declaration.PackageDeclarationTranslator;
044    import org.jetbrains.k2js.translate.expression.ExpressionVisitor;
045    import org.jetbrains.k2js.translate.expression.FunctionTranslator;
046    import org.jetbrains.k2js.translate.expression.PatternTranslator;
047    import org.jetbrains.k2js.translate.test.JSRhinoUnitTester;
048    import org.jetbrains.k2js.translate.test.JSTestGenerator;
049    import org.jetbrains.k2js.translate.test.JSTester;
050    import org.jetbrains.k2js.translate.test.QUnitTester;
051    import org.jetbrains.k2js.translate.utils.JsAstUtils;
052    import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
053    
054    import java.util.Collection;
055    import java.util.Collections;
056    import java.util.List;
057    
058    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
059    import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
060    import static org.jetbrains.k2js.translate.utils.JsAstUtils.toStringLiteralList;
061    import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
062    
063    /**
064     * This class provides a interface which all translators use to interact with each other.
065     * Goal is to simplify interaction between translators.
066     */
067    public final class Translation {
068    
069        private Translation() {
070        }
071    
072        @NotNull
073        public static FunctionTranslator functionTranslator(@NotNull JetDeclarationWithBody function,
074                @NotNull TranslationContext context) {
075            return FunctionTranslator.newInstance(function, context);
076        }
077    
078        @NotNull
079        public static PatternTranslator patternTranslator(@NotNull TranslationContext context) {
080            return PatternTranslator.newInstance(context);
081        }
082    
083        @NotNull
084        public static JsNode translateExpression(@NotNull JetExpression expression, @NotNull TranslationContext context) {
085            return translateExpression(expression, context, context.dynamicContext().jsBlock());
086        }
087    
088        @NotNull
089        public static JsNode translateExpression(@NotNull JetExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block) {
090            JsExpression aliasForExpression = context.aliasingContext().getAliasForExpression(expression);
091            if (aliasForExpression != null) {
092                return aliasForExpression;
093            }
094    
095            TranslationContext innerContext = context.innerBlock();
096            JsNode result = doTranslateExpression(expression, innerContext);
097            context.moveVarsFrom(innerContext);
098            block.getStatements().addAll(innerContext.dynamicContext().jsBlock().getStatements());
099    
100            if (BindingContextUtilPackage.isUnreachableCode(expression, context.bindingContext())) {
101                return context.getEmptyExpression();
102            }
103    
104            return result;
105        }
106    
107        //NOTE: use with care
108        @NotNull
109        public static JsNode doTranslateExpression(JetExpression expression, TranslationContext context) {
110            return expression.accept(new ExpressionVisitor(), context);
111        }
112    
113        @NotNull
114        public static JsExpression translateAsExpression(@NotNull JetExpression expression, @NotNull TranslationContext context) {
115            return translateAsExpression(expression, context, context.dynamicContext().jsBlock());
116        }
117    
118        @NotNull
119        public static JsExpression translateAsExpression(
120                @NotNull JetExpression expression,
121                @NotNull TranslationContext context,
122                @NotNull JsBlock block
123        ) {
124            JsNode jsNode = translateExpression(expression, context, block);
125            if (jsNode instanceof  JsExpression) {
126                return (JsExpression) jsNode;
127            }
128    
129            assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString();
130            if (BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext())) {
131                TemporaryVariable result = context.declareTemporary(null);
132                AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
133                block.getStatements().add(mutateLastExpression(jsNode, saveResultToTemporaryMutator));
134                return result.reference();
135            }
136    
137            block.getStatements().add(convertToStatement(jsNode));
138            return context.getEmptyExpression();
139        }
140    
141        @NotNull
142        public static JsStatement translateAsStatement(@NotNull JetExpression expression, @NotNull TranslationContext context) {
143            return translateAsStatement(expression, context, context.dynamicContext().jsBlock());
144        }
145    
146        @NotNull
147        public static JsStatement translateAsStatement(
148                @NotNull JetExpression expression,
149                @NotNull TranslationContext context,
150                @NotNull JsBlock block) {
151            return convertToStatement(translateExpression(expression, context, block));
152        }
153    
154        @NotNull
155        public static JsStatement translateAsStatementAndMergeInBlockIfNeeded(
156                @NotNull JetExpression expression,
157                @NotNull TranslationContext context
158        ) {
159            JsBlock block = new JsBlock();
160            JsNode node = translateExpression(expression, context, block);
161            return JsAstUtils.mergeStatementInBlockIfNeeded(convertToStatement(node), block);
162        }
163    
164        @NotNull
165        public static JsProgram generateAst(@NotNull BindingContext bindingContext,
166                @NotNull Collection<JetFile> files, @NotNull MainCallParameters mainCallParameters,
167                @NotNull ModuleDescriptor moduleDescriptor,
168                @NotNull Config config)
169                throws TranslationException {
170            try {
171                JsProgram program = doGenerateAst(bindingContext, files, mainCallParameters, moduleDescriptor, config);
172                return config.isInlineEnabled() ? JsInliner.process(program) : program;
173            }
174            catch (UnsupportedOperationException e) {
175                throw new UnsupportedFeatureException("Unsupported feature used.", e);
176            }
177            catch (Throwable e) {
178                throw new TranslationInternalException(e);
179            }
180        }
181    
182        @NotNull
183        private static JsProgram doGenerateAst(@NotNull BindingContext bindingContext, @NotNull Collection<JetFile> files,
184                @NotNull MainCallParameters mainCallParameters,
185                @NotNull ModuleDescriptor moduleDescriptor,
186                @NotNull Config config) throws MainFunctionNotFoundException {
187            StaticContext staticContext = StaticContext.generateStaticContext(bindingContext, config, moduleDescriptor);
188            JsProgram program = staticContext.getProgram();
189            JsBlock block = program.getGlobalBlock();
190    
191            JsFunction rootFunction = JsAstUtils.createPackage(block.getStatements(), program.getScope());
192            JsBlock rootBlock = rootFunction.getBody();
193            List<JsStatement> statements = rootBlock.getStatements();
194            statements.add(program.getStringLiteral("use strict").makeStmt());
195    
196            TranslationContext context = TranslationContext.rootContext(staticContext, rootFunction);
197            statements.addAll(PackageDeclarationTranslator.translateFiles(files, context));
198            defineModule(context, statements, config.getModuleId());
199    
200            if (mainCallParameters.shouldBeGenerated()) {
201                JsStatement statement = generateCallToMain(context, files, mainCallParameters.arguments());
202                if (statement != null) {
203                    statements.add(statement);
204                }
205            }
206            mayBeGenerateTests(files, config, rootBlock, context);
207            return context.program();
208        }
209    
210        private static void defineModule(@NotNull TranslationContext context, @NotNull List<JsStatement> statements, @NotNull String moduleId) {
211            JsName rootPackageName = context.scope().findName(Namer.getRootPackageName());
212            if (rootPackageName != null) {
213                statements.add(new JsInvocation(context.namer().kotlin("defineModule"), context.program().getStringLiteral(moduleId),
214                                                rootPackageName.makeRef()).makeStmt());
215            }
216        }
217    
218        private static void mayBeGenerateTests(@NotNull Collection<JetFile> files, @NotNull Config config,
219                @NotNull JsBlock rootBlock, @NotNull TranslationContext context) {
220            JSTester tester = config.isTestConfig() ? new JSRhinoUnitTester() : new QUnitTester();
221            tester.initialize(context, rootBlock);
222            JSTestGenerator.generateTestCalls(context, files, tester);
223            tester.deinitialize();
224        }
225    
226        //TODO: determine whether should throw exception
227        @Nullable
228        private static JsStatement generateCallToMain(@NotNull TranslationContext context, @NotNull Collection<JetFile> files,
229                @NotNull List<String> arguments) throws MainFunctionNotFoundException {
230            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(context.bindingContext());
231            JetNamedFunction mainFunction = mainFunctionDetector.getMainFunction(files);
232            if (mainFunction == null) {
233                return null;
234            }
235            FunctionDescriptor functionDescriptor = getFunctionDescriptor(context.bindingContext(), mainFunction);
236            JsArrayLiteral argument = new JsArrayLiteral(toStringLiteralList(arguments, context.program()));
237            return CallTranslator.INSTANCE$.buildCall(context, functionDescriptor, Collections.singletonList(argument), null).makeStmt();
238        }
239    }