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.psi.*;
024    import org.jetbrains.jet.lang.resolve.BindingContext;
025    import org.jetbrains.k2js.config.Config;
026    import org.jetbrains.k2js.facade.MainCallParameters;
027    import org.jetbrains.k2js.facade.exceptions.MainFunctionNotFoundException;
028    import org.jetbrains.k2js.facade.exceptions.TranslationException;
029    import org.jetbrains.k2js.facade.exceptions.TranslationInternalException;
030    import org.jetbrains.k2js.facade.exceptions.UnsupportedFeatureException;
031    import org.jetbrains.k2js.translate.context.Namer;
032    import org.jetbrains.k2js.translate.context.StaticContext;
033    import org.jetbrains.k2js.translate.context.TranslationContext;
034    import org.jetbrains.k2js.translate.declaration.NamespaceDeclarationTranslator;
035    import org.jetbrains.k2js.translate.expression.ExpressionVisitor;
036    import org.jetbrains.k2js.translate.expression.FunctionTranslator;
037    import org.jetbrains.k2js.translate.expression.PatternTranslator;
038    import org.jetbrains.k2js.translate.expression.WhenTranslator;
039    import org.jetbrains.k2js.translate.reference.CallBuilder;
040    import org.jetbrains.k2js.translate.test.JSTestGenerator;
041    import org.jetbrains.k2js.translate.test.JSTester;
042    import org.jetbrains.k2js.translate.utils.JsAstUtils;
043    import org.jetbrains.k2js.translate.utils.dangerous.DangerousData;
044    import org.jetbrains.k2js.translate.utils.dangerous.DangerousTranslator;
045    
046    import java.util.Collection;
047    import java.util.Collections;
048    import java.util.List;
049    
050    import static org.jetbrains.jet.plugin.JetMainDetector.getMainFunction;
051    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
052    import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
053    import static org.jetbrains.k2js.translate.utils.dangerous.DangerousData.collect;
054    
055    /**
056     * This class provides a interface which all translators use to interact with each other.
057     * Goal is to simplify interaction between translators.
058     */
059    public final class Translation {
060    
061        private Translation() {
062        }
063    
064        @NotNull
065        public static FunctionTranslator functionTranslator(@NotNull JetDeclarationWithBody function,
066                @NotNull TranslationContext context) {
067            return FunctionTranslator.newInstance(function, context);
068        }
069    
070        @NotNull
071        private static List<JsStatement> translateFiles(@NotNull Collection<JetFile> files, @NotNull TranslationContext context) {
072            return NamespaceDeclarationTranslator.translateFiles(files, context);
073        }
074    
075        @NotNull
076        public static PatternTranslator patternTranslator(@NotNull TranslationContext context) {
077            return PatternTranslator.newInstance(context);
078        }
079    
080        @NotNull
081        public static JsNode translateExpression(@NotNull JetExpression expression, @NotNull TranslationContext context) {
082            JsName aliasForExpression = context.aliasingContext().getAliasForExpression(expression);
083            if (aliasForExpression != null) {
084                return aliasForExpression.makeRef();
085            }
086            DangerousData data = collect(expression, context);
087            if (data.shouldBeTranslated()) {
088                return DangerousTranslator.translate(data, context);
089            }
090            return doTranslateExpression(expression, context);
091        }
092    
093        //NOTE: use with care
094        @NotNull
095        public static JsNode doTranslateExpression(JetExpression expression, TranslationContext context) {
096            return expression.accept(new ExpressionVisitor(), context);
097        }
098    
099        @NotNull
100        public static JsExpression translateAsExpression(@NotNull JetExpression expression,
101                @NotNull TranslationContext context) {
102            return convertToExpression(translateExpression(expression, context));
103        }
104    
105        @NotNull
106        public static JsStatement translateAsStatement(@NotNull JetExpression expression,
107                @NotNull TranslationContext context) {
108            return convertToStatement(translateExpression(expression, context));
109        }
110    
111        @Nullable
112        public static JsNode translateWhenExpression(@NotNull JetWhenExpression expression,
113                @NotNull TranslationContext context) {
114            return WhenTranslator.translate(expression, context);
115        }
116    
117        @NotNull
118        public static JsProgram generateAst(@NotNull BindingContext bindingContext,
119                @NotNull Collection<JetFile> files, @NotNull MainCallParameters mainCallParameters,
120                @NotNull Config config)
121                throws TranslationException {
122            try {
123                return doGenerateAst(bindingContext, files, mainCallParameters, config);
124            }
125            catch (UnsupportedOperationException e) {
126                throw new UnsupportedFeatureException("Unsupported feature used.", e);
127            }
128            catch (Throwable e) {
129                throw new TranslationInternalException(e);
130            }
131        }
132    
133        @NotNull
134        private static JsProgram doGenerateAst(@NotNull BindingContext bindingContext, @NotNull Collection<JetFile> files,
135                @NotNull MainCallParameters mainCallParameters,
136                @NotNull Config config) throws MainFunctionNotFoundException {
137            //TODO: move some of the code somewhere
138            StaticContext staticContext = StaticContext.generateStaticContext(bindingContext, config.getTarget());
139            JsProgram program = staticContext.getProgram();
140            JsBlock block = program.getGlobalBlock();
141    
142            JsFunction rootFunction = JsAstUtils.createPackage(block.getStatements(), program.getScope());
143            JsBlock rootBlock = rootFunction.getBody();
144            List<JsStatement> statements = rootBlock.getStatements();
145            statements.add(program.getStringLiteral("use strict").makeStmt());
146    
147            TranslationContext context = TranslationContext.rootContext(staticContext, rootFunction);
148            staticContext.getLiteralFunctionTranslator().setRootContext(context);
149            statements.addAll(translateFiles(files, context));
150            defineModule(context, statements, config.getModuleId());
151    
152            if (mainCallParameters.shouldBeGenerated()) {
153                JsStatement statement = generateCallToMain(context, files, mainCallParameters.arguments());
154                if (statement != null) {
155                    statements.add(statement);
156                }
157            }
158            mayBeGenerateTests(files, config, rootBlock, context);
159            return context.program();
160        }
161    
162        private static void defineModule(@NotNull TranslationContext context, @NotNull List<JsStatement> statements, @NotNull String moduleId) {
163            JsName rootNamespaceName = context.scope().findName(Namer.getRootNamespaceName());
164            if (rootNamespaceName != null) {
165                statements.add(new JsInvocation(context.namer().kotlin("defineModule"), context.program().getStringLiteral(moduleId),
166                                                rootNamespaceName.makeRef()).makeStmt());
167            }
168        }
169    
170        private static void mayBeGenerateTests(@NotNull Collection<JetFile> files, @NotNull Config config,
171                @NotNull JsBlock rootBlock, @NotNull TranslationContext context) {
172            JSTester tester = config.getTester();
173            if (tester != null) {
174                tester.initialize(context, rootBlock);
175                JSTestGenerator.generateTestCalls(context, files, tester);
176                tester.deinitialize();
177            }
178        }
179    
180        //TODO: determine whether should throw exception
181        @Nullable
182        private static JsStatement generateCallToMain(@NotNull TranslationContext context, @NotNull Collection<JetFile> files,
183                @NotNull List<String> arguments) throws MainFunctionNotFoundException {
184            JetNamedFunction mainFunction = getMainFunction(files);
185            if (mainFunction == null) {
186                return null;
187            }
188            JsInvocation translatedCall = generateInvocation(context, mainFunction);
189            setArguments(context, arguments, translatedCall);
190            return translatedCall.makeStmt();
191        }
192    
193    
194        @NotNull
195        private static JsInvocation generateInvocation(@NotNull TranslationContext context, @NotNull JetNamedFunction mainFunction) {
196            FunctionDescriptor functionDescriptor = getFunctionDescriptor(context.bindingContext(), mainFunction);
197            JsExpression translatedCall = CallBuilder.build(context).descriptor(functionDescriptor).translate();
198            assert translatedCall instanceof JsInvocation;
199            return (JsInvocation) translatedCall;
200        }
201    
202        private static void setArguments(@NotNull TranslationContext context, @NotNull List<String> arguments,
203                @NotNull JsInvocation translatedCall) {
204            JsArrayLiteral arrayLiteral = new JsArrayLiteral(toStringLiteralList(arguments, context.program()));
205            JsAstUtils.setArguments(translatedCall, Collections.<JsExpression>singletonList(arrayLiteral));
206        }
207    }