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.expression;
018    
019    
020    import com.google.dart.compiler.backend.js.ast.JsFunction;
021    import com.google.dart.compiler.backend.js.ast.JsName;
022    import com.google.dart.compiler.backend.js.ast.JsParameter;
023    import com.google.dart.compiler.backend.js.ast.JsPropertyInitializer;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
027    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
028    import org.jetbrains.jet.lang.descriptors.Modality;
029    import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
030    import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
031    import org.jetbrains.jet.lang.psi.JetExpression;
032    import org.jetbrains.jet.lang.psi.JetFunctionLiteral;
033    import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression;
034    import org.jetbrains.k2js.translate.context.Namer;
035    import org.jetbrains.k2js.translate.context.TranslationContext;
036    import org.jetbrains.k2js.translate.general.AbstractTranslator;
037    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038    import org.jetbrains.k2js.translate.utils.TranslationUtils;
039    
040    import java.util.ArrayList;
041    import java.util.List;
042    
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
044    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
045    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
046    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters;
047    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
048    
049    public final class FunctionTranslator extends AbstractTranslator {
050        @NotNull
051        public static FunctionTranslator newInstance(@NotNull JetDeclarationWithBody function,
052                @NotNull TranslationContext context) {
053            return new FunctionTranslator(function, context);
054        }
055    
056        @NotNull
057        private final TranslationContext functionBodyContext;
058        @NotNull
059        private final JetDeclarationWithBody functionDeclaration;
060        @Nullable
061        private JsName extensionFunctionReceiverName;
062        @NotNull
063        private final JsFunction functionObject;
064        @NotNull
065        private final FunctionDescriptor descriptor;
066    
067        private FunctionTranslator(@NotNull JetDeclarationWithBody functionDeclaration,
068                @NotNull TranslationContext context) {
069            super(context);
070            this.descriptor = getFunctionDescriptor(context.bindingContext(), functionDeclaration);
071            this.functionDeclaration = functionDeclaration;
072            this.functionObject = context().getFunctionObject(descriptor);
073            assert this.functionObject.getParameters().isEmpty()
074                    : message(bindingContext(), descriptor, "Function " + functionDeclaration.getText() + " processed for the second time.");
075            //NOTE: it's important we compute the context before we start the computation
076            this.functionBodyContext = getFunctionBodyContext();
077        }
078    
079        @NotNull
080        private TranslationContext getFunctionBodyContext() {
081            if (isExtensionFunction()) {
082                return getFunctionBodyContextForExtensionFunction();
083            }
084            return getContextWithFunctionBodyBlock();
085        }
086    
087        @NotNull
088        private TranslationContext getFunctionBodyContextForExtensionFunction() {
089            TranslationContext contextWithFunctionBodyBlock = getContextWithFunctionBodyBlock();
090            extensionFunctionReceiverName = contextWithFunctionBodyBlock.scope().declareName(Namer.getReceiverParameterName());
091            DeclarationDescriptor expectedReceiverDescriptor = getExpectedReceiverDescriptor(descriptor);
092            assert expectedReceiverDescriptor != null;
093            return contextWithFunctionBodyBlock.innerContextWithThisAliased(expectedReceiverDescriptor, extensionFunctionReceiverName);
094        }
095    
096        @NotNull
097        private TranslationContext getContextWithFunctionBodyBlock() {
098            return context().newDeclaration(functionDeclaration).innerBlock(functionObject.getBody());
099        }
100    
101        @NotNull
102        public JsFunction translateAsLocalFunction() {
103            JsName functionName = context().getNameForElement(functionDeclaration);
104            generateFunctionObject();
105            functionObject.setName(functionName);
106            return functionObject;
107        }
108    
109        @NotNull
110        public JsPropertyInitializer translateAsEcma5PropertyDescriptor() {
111            generateFunctionObject();
112            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(functionObject, descriptor, context());
113        }
114    
115        @NotNull
116        public JsPropertyInitializer translateAsMethod() {
117            JsName functionName = context().getNameForElement(functionDeclaration);
118            generateFunctionObject();
119            return new JsPropertyInitializer(functionName.makeRef(), functionObject);
120        }
121    
122        private void generateFunctionObject() {
123            setParameters(functionObject, translateParameters());
124            translateBody();
125        }
126    
127        private void translateBody() {
128            JetExpression jetBodyExpression = functionDeclaration.getBodyExpression();
129            if (jetBodyExpression == null) {
130                assert descriptor.getModality().equals(Modality.ABSTRACT);
131                return;
132            }
133            functionObject.getBody().getStatements().addAll(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext).getStatements());
134        }
135    
136        @NotNull
137        private List<JsParameter> translateParameters() {
138            List<JsParameter> jsParameters = new ArrayList<JsParameter>();
139            mayBeAddThisParameterForExtensionFunction(jsParameters);
140            addParameters(jsParameters, descriptor, context());
141            return jsParameters;
142        }
143    
144        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
145            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
146                list.add(new JsParameter(context.getNameForDescriptor(valueParameter)));
147            }
148        }
149    
150        private void mayBeAddThisParameterForExtensionFunction(@NotNull List<JsParameter> jsParameters) {
151            if (isExtensionFunction()) {
152                assert extensionFunctionReceiverName != null;
153                jsParameters.add(new JsParameter(extensionFunctionReceiverName));
154            }
155        }
156    
157        private boolean isExtensionFunction() {
158            return JsDescriptorUtils.isExtension(descriptor) && !(functionDeclaration instanceof JetFunctionLiteral);
159        }
160    }