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