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.jet.codegen;
018
019import com.google.common.collect.Lists;
020import com.intellij.openapi.util.Pair;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.asm4.MethodVisitor;
023import org.jetbrains.asm4.Type;
024import org.jetbrains.asm4.commons.InstructionAdapter;
025import org.jetbrains.asm4.commons.Method;
026import org.jetbrains.jet.codegen.context.CodegenContext;
027import org.jetbrains.jet.codegen.context.FieldOwnerContext;
028import org.jetbrains.jet.codegen.context.MethodContext;
029import org.jetbrains.jet.codegen.context.ScriptContext;
030import org.jetbrains.jet.codegen.signature.JvmMethodSignature;
031import org.jetbrains.jet.codegen.state.GenerationState;
032import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
033import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
034import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
035import org.jetbrains.jet.lang.psi.*;
036import org.jetbrains.jet.lang.resolve.BindingContext;
037import org.jetbrains.jet.lang.resolve.ScriptNameUtil;
038import org.jetbrains.jet.lang.resolve.java.JvmClassName;
039
040import javax.inject.Inject;
041import java.util.Collections;
042import java.util.List;
043
044import static org.jetbrains.asm4.Opcodes.*;
045import static org.jetbrains.jet.codegen.binding.CodegenBinding.*;
046import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
047
048public class ScriptCodegen extends MemberCodegen {
049
050    @NotNull
051    private ClassFileFactory classFileFactory;
052
053    private List<ScriptDescriptor> earlierScripts;
054    private Method scriptConstructorMethod;
055
056    public ScriptCodegen(@NotNull GenerationState state) {
057        super(state, null);
058    }
059
060    @Inject
061    public void setClassFileFactory(@NotNull ClassFileFactory classFileFactory) {
062        this.classFileFactory = classFileFactory;
063    }
064
065    public void generate(JetScript scriptDeclaration) {
066
067        ScriptDescriptor scriptDescriptor = state.getBindingContext().get(BindingContext.SCRIPT, scriptDeclaration);
068
069        assert scriptDescriptor != null;
070        ClassDescriptor classDescriptorForScript = bindingContext.get(CLASS_FOR_SCRIPT, scriptDescriptor);
071        assert classDescriptorForScript != null;
072
073        ScriptContext context =
074                (ScriptContext) CodegenContext.STATIC
075                        .intoScript(scriptDescriptor, classDescriptorForScript);
076
077        JvmClassName className = bindingContext.get(FQN, classDescriptorForScript);
078        assert className != null;
079
080        ClassBuilder classBuilder = classFileFactory.newVisitor(className.getInternalName(), scriptDeclaration.getContainingFile()
081        );
082        classBuilder.defineClass(scriptDeclaration,
083                                 V1_6,
084                                 ACC_PUBLIC,
085                                 className.getInternalName(),
086                                 null,
087                                 "java/lang/Object",
088                                 new String[0]);
089
090        genMembers(scriptDeclaration, context, classBuilder);
091        genFieldsForParameters(scriptDescriptor, classBuilder);
092        genConstructor(scriptDeclaration, scriptDescriptor, classDescriptorForScript, classBuilder,
093                       context.intoFunction(scriptDescriptor.getScriptCodeDescriptor()),
094                       earlierScripts);
095
096        classBuilder.done();
097    }
098
099    private void genConstructor(
100            @NotNull JetScript scriptDeclaration,
101            @NotNull ScriptDescriptor scriptDescriptor,
102            @NotNull ClassDescriptor classDescriptorForScript,
103            @NotNull ClassBuilder classBuilder,
104            @NotNull MethodContext context,
105            @NotNull List<ScriptDescriptor> importedScripts
106    ) {
107
108        Type blockType = typeMapper.mapType(scriptDescriptor.getReturnType());
109
110        classBuilder.newField(null, ACC_PUBLIC | ACC_FINAL, ScriptNameUtil.LAST_EXPRESSION_VALUE_FIELD_NAME,
111                              blockType.getDescriptor(), null, null);
112
113        JvmMethodSignature jvmSignature = typeMapper.mapScriptSignature(scriptDescriptor, importedScripts);
114
115        state.getScriptCodegen().setScriptConstructorMethod(jvmSignature.getAsmMethod());
116
117        MethodVisitor mv = classBuilder.newMethod(
118                scriptDeclaration, ACC_PUBLIC, jvmSignature.getAsmMethod().getName(), jvmSignature.getAsmMethod().getDescriptor(),
119                null, null);
120
121        mv.visitCode();
122
123        InstructionAdapter instructionAdapter = new InstructionAdapter(mv);
124
125        JvmClassName className = bindingContext.get(FQN, classDescriptorForScript);
126        assert className != null;
127
128        instructionAdapter.load(0, className.getAsmType());
129        instructionAdapter.invokespecial("java/lang/Object", "<init>", "()V");
130
131        instructionAdapter.load(0, className.getAsmType());
132
133        FrameMap frameMap = context.prepareFrame(typeMapper);
134
135        for (ScriptDescriptor importedScript : importedScripts) {
136            frameMap.enter(importedScript, OBJECT_TYPE);
137        }
138
139        Type[] argTypes = jvmSignature.getAsmMethod().getArgumentTypes();
140        int add = 0;
141
142        for (int i = 0; i < scriptDescriptor.getValueParameters().size(); i++) {
143            ValueParameterDescriptor parameter = scriptDescriptor.getValueParameters().get(i);
144            frameMap.enter(parameter, argTypes[i + add]);
145        }
146
147        ImplementationBodyCodegen.generateInitializers(
148                new ExpressionCodegen(instructionAdapter, frameMap, Type.VOID_TYPE, context, state),
149                scriptDeclaration.getDeclarations(),
150                bindingContext,
151                state);
152
153        int offset = 1;
154
155        for (ScriptDescriptor earlierScript : importedScripts) {
156            JvmClassName earlierClassName = classNameForScriptDescriptor(bindingContext, earlierScript);
157            instructionAdapter.load(0, className.getAsmType());
158            instructionAdapter.load(offset, earlierClassName.getAsmType());
159            offset += earlierClassName.getAsmType().getSize();
160            instructionAdapter.putfield(className.getInternalName(), getScriptFieldName(earlierScript),
161                                        earlierClassName.getAsmType().getDescriptor());
162        }
163
164        for (ValueParameterDescriptor parameter : scriptDescriptor.getValueParameters()) {
165            Type parameterType = typeMapper.mapType(parameter.getType());
166            instructionAdapter.load(0, className.getAsmType());
167            instructionAdapter.load(offset, parameterType);
168            offset += parameterType.getSize();
169            instructionAdapter.putfield(className.getInternalName(), parameter.getName().getIdentifier(), parameterType.getDescriptor());
170        }
171
172        StackValue stackValue =
173                new ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, context, state).gen(scriptDeclaration.getBlockExpression());
174        if (stackValue.type != Type.VOID_TYPE) {
175            stackValue.put(stackValue.type, instructionAdapter);
176            instructionAdapter
177                    .putfield(className.getInternalName(), ScriptNameUtil.LAST_EXPRESSION_VALUE_FIELD_NAME, blockType.getDescriptor());
178        }
179
180        instructionAdapter.areturn(Type.VOID_TYPE);
181        mv.visitMaxs(-1, -1);
182        mv.visitEnd();
183    }
184
185    private void genFieldsForParameters(@NotNull ScriptDescriptor script, @NotNull ClassBuilder classBuilder) {
186        for (ScriptDescriptor earlierScript : earlierScripts) {
187            JvmClassName earlierClassName;
188            earlierClassName = classNameForScriptDescriptor(bindingContext, earlierScript);
189            int access = ACC_PRIVATE | ACC_FINAL;
190            classBuilder.newField(null, access, getScriptFieldName(earlierScript), earlierClassName.getDescriptor(), null, null);
191        }
192
193        for (ValueParameterDescriptor parameter : script.getValueParameters()) {
194            Type parameterType = typeMapper.mapType(parameter);
195            int access = ACC_PUBLIC | ACC_FINAL;
196            classBuilder.newField(null, access, parameter.getName().getIdentifier(), parameterType.getDescriptor(), null, null);
197        }
198    }
199
200    private void genMembers(@NotNull JetScript scriptDeclaration, @NotNull FieldOwnerContext context, @NotNull ClassBuilder classBuilder) {
201        for (JetDeclaration decl : scriptDeclaration.getDeclarations()) {
202            genFunctionOrProperty(context, (JetTypeParameterListOwner) decl, classBuilder);
203        }
204    }
205
206    public void registerEarlierScripts(List<Pair<ScriptDescriptor, JvmClassName>> earlierScripts) {
207        for (Pair<ScriptDescriptor, JvmClassName> t : earlierScripts) {
208            ScriptDescriptor earlierDescriptor = t.first;
209            JvmClassName earlierClassName = t.second;
210
211            registerClassNameForScript(state.getBindingTrace(), earlierDescriptor, earlierClassName);
212        }
213
214        List<ScriptDescriptor> earlierScriptDescriptors = Lists.newArrayList();
215        for (Pair<ScriptDescriptor, JvmClassName> t : earlierScripts) {
216            ScriptDescriptor earlierDescriptor = t.first;
217            earlierScriptDescriptors.add(earlierDescriptor);
218        }
219        this.earlierScripts = earlierScriptDescriptors;
220    }
221
222    protected int getScriptIndex(@NotNull ScriptDescriptor scriptDescriptor) {
223        int index = earlierScripts.indexOf(scriptDescriptor);
224        if (index < 0) {
225            throw new IllegalStateException("Unregistered script: " + scriptDescriptor);
226        }
227        return index + 1;
228    }
229
230    public String getScriptFieldName(@NotNull ScriptDescriptor scriptDescriptor) {
231        return "script$" + getScriptIndex(scriptDescriptor);
232    }
233
234    public void setScriptConstructorMethod(Method scriptConstructorMethod) {
235        this.scriptConstructorMethod = scriptConstructorMethod;
236    }
237
238    public Method getScriptConstructorMethod() {
239        return scriptConstructorMethod;
240    }
241
242    public void compileScript(
243            @NotNull JetScript script,
244            @NotNull JvmClassName className,
245            @NotNull List<Pair<ScriptDescriptor, JvmClassName>> earlierScripts,
246            @NotNull CompilationErrorHandler errorHandler
247    ) {
248        registerEarlierScripts(earlierScripts);
249        registerClassNameForScript(state.getBindingTrace(), script, className);
250
251        state.beforeCompile();
252        KotlinCodegenFacade.generateNamespace(
253                state,
254                JetPsiUtil.getFQName((JetFile) script.getContainingFile()),
255                Collections.singleton((JetFile) script.getContainingFile()),
256                errorHandler);
257    }
258}