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