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.jet.cli.jvm.repl;
018
019 import com.google.common.base.Predicates;
020 import com.google.common.base.Throwables;
021 import com.google.common.collect.Lists;
022 import com.intellij.openapi.Disposable;
023 import com.intellij.openapi.project.Project;
024 import com.intellij.openapi.util.Pair;
025 import com.intellij.openapi.vfs.CharsetToolkit;
026 import com.intellij.psi.PsiFile;
027 import com.intellij.psi.PsiFileFactory;
028 import com.intellij.psi.impl.PsiFileFactoryImpl;
029 import com.intellij.testFramework.LightVirtualFile;
030 import org.jetbrains.annotations.NotNull;
031 import org.jetbrains.annotations.Nullable;
032 import org.jetbrains.asm4.Type;
033 import org.jetbrains.jet.OutputFile;
034 import org.jetbrains.jet.analyzer.AnalyzeExhaust;
035 import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
036 import org.jetbrains.jet.cli.common.messages.MessageCollector;
037 import org.jetbrains.jet.cli.common.messages.MessageCollectorToString;
038 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
039 import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
040 import org.jetbrains.jet.codegen.ClassBuilderFactories;
041 import org.jetbrains.jet.codegen.CompilationErrorHandler;
042 import org.jetbrains.jet.codegen.KotlinCodegenFacade;
043 import org.jetbrains.jet.codegen.state.GenerationState;
044 import org.jetbrains.jet.config.CompilerConfiguration;
045 import org.jetbrains.jet.di.InjectorForTopDownAnalyzerForJvm;
046 import org.jetbrains.jet.lang.descriptors.ModuleDescriptorImpl;
047 import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
048 import org.jetbrains.jet.lang.descriptors.impl.NamespaceDescriptorImpl;
049 import org.jetbrains.jet.lang.descriptors.impl.NamespaceLikeBuilderDummy;
050 import org.jetbrains.jet.lang.psi.JetFile;
051 import org.jetbrains.jet.lang.psi.JetPsiUtil;
052 import org.jetbrains.jet.lang.psi.JetScript;
053 import org.jetbrains.jet.lang.resolve.*;
054 import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
055 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
056 import org.jetbrains.jet.lang.resolve.name.FqName;
057 import org.jetbrains.jet.lang.resolve.scopes.JetScope;
058 import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
059 import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl;
060 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
061 import org.jetbrains.jet.plugin.JetLanguage;
062 import org.jetbrains.jet.utils.ExceptionUtils;
063
064 import java.io.File;
065 import java.io.PrintWriter;
066 import java.lang.reflect.Constructor;
067 import java.lang.reflect.Field;
068 import java.net.MalformedURLException;
069 import java.net.URL;
070 import java.net.URLClassLoader;
071 import java.util.Collections;
072 import java.util.List;
073
074 import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
075 import static org.jetbrains.jet.codegen.binding.CodegenBinding.registerClassNameForScript;
076
077 public class ReplInterpreter {
078
079 private int lineNumber = 0;
080 @Nullable
081 private JetScope lastLineScope;
082 private List<EarlierLine> earlierLines = Lists.newArrayList();
083 private List<String> previousIncompleteLines = Lists.newArrayList();
084 private final ReplClassLoader classLoader;
085
086 @NotNull
087 private final InjectorForTopDownAnalyzerForJvm injector;
088 @NotNull
089 private final JetCoreEnvironment jetCoreEnvironment;
090 @NotNull
091 private final BindingTraceContext trace;
092 @NotNull
093 private final ModuleDescriptorImpl module;
094
095 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
096 jetCoreEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration);
097 Project project = jetCoreEnvironment.getProject();
098 trace = new BindingTraceContext();
099 module = AnalyzerFacadeForJVM.createJavaModule("<repl>");
100 TopDownAnalysisParameters topDownAnalysisParameters = new TopDownAnalysisParameters(
101 Predicates.<PsiFile>alwaysTrue(),
102 false,
103 true,
104 Collections.<AnalyzerScriptParameter>emptyList());
105 injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module);
106 module.setModuleConfiguration(injector.getJavaBridgeConfiguration());
107
108 List<URL> classpath = Lists.newArrayList();
109
110 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
111 try {
112 classpath.add(file.toURI().toURL());
113 }
114 catch (MalformedURLException e) {
115 throw ExceptionUtils.rethrow(e);
116 }
117 }
118
119 classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0])));
120 }
121
122 public enum LineResultType {
123 SUCCESS,
124 ERROR,
125 INCOMPLETE,
126 }
127
128 public static class LineResult {
129
130 private final Object value;
131 private final boolean unit;
132 private final String errorText;
133 @NotNull
134 private final LineResultType type;
135
136 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) {
137 this.value = value;
138 this.unit = unit;
139 this.errorText = errorText;
140 this.type = type;
141 }
142
143 @NotNull
144 public LineResultType getType() {
145 return type;
146 }
147
148 private void checkSuccessful() {
149 if (!(getType() == LineResultType.SUCCESS)) {
150 throw new IllegalStateException("it is error");
151 }
152 }
153
154 public Object getValue() {
155 checkSuccessful();
156 return value;
157 }
158
159 public boolean isUnit() {
160 checkSuccessful();
161 return unit;
162 }
163
164 @NotNull
165 public String getErrorText() {
166 return errorText;
167 }
168
169 public static LineResult successful(Object value, boolean unit) {
170 return new LineResult(value, unit, null, LineResultType.SUCCESS);
171 }
172
173 public static LineResult error(@NotNull String errorText) {
174 if (errorText.isEmpty()) {
175 errorText = "<unknown error>";
176 }
177 else if (!errorText.endsWith("\n")) {
178 errorText = errorText + "\n";
179 }
180 return new LineResult(null, false, errorText, LineResultType.ERROR);
181 }
182
183 public static LineResult incomplete() {
184 return new LineResult(null, false, null, LineResultType.INCOMPLETE);
185 }
186 }
187
188 @NotNull
189 public LineResult eval(@NotNull String line) {
190 ++lineNumber;
191
192 FqName scriptFqName = new FqName("Line" + lineNumber);
193 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName);
194
195 StringBuilder fullText = new StringBuilder();
196 for (String prevLine : previousIncompleteLines) {
197 fullText.append(prevLine + "\n");
198 }
199 fullText.append(line);
200
201 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, fullText.toString());
202 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
203 JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
204
205 MessageCollectorToString errorCollector = new MessageCollectorToString();
206
207 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport =
208 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector);
209
210 if (syntaxErrorReport.isOnlyErrorAtEof()) {
211 previousIncompleteLines.add(line);
212 return LineResult.incomplete();
213 }
214
215 previousIncompleteLines.clear();
216
217 if (syntaxErrorReport.isHasErrors()) {
218 return LineResult.error(errorCollector.getString());
219 }
220
221 injector.getTopDownAnalyzer().prepareForTheNextReplLine();
222 trace.clearDiagnostics();
223
224 psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber);
225
226 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
227 if (scriptDescriptor == null) {
228 return LineResult.error(errorCollector.getString());
229 }
230
231 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
232
233 for (EarlierLine earlierLine : earlierLines) {
234 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType()));
235 }
236
237 BindingContext bindingContext = AnalyzeExhaust.success(trace.getBindingContext(), module).getBindingContext();
238 GenerationState generationState = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES,
239 bindingContext, Collections.singletonList(psiFile));
240
241 compileScript(psiFile.getScript(), scriptClassType, earlierScripts, generationState,
242 CompilationErrorHandler.THROW_EXCEPTION);
243
244 for (OutputFile outputFile : generationState.getFactory().asList()) {
245 classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), outputFile.asByteArray());
246 }
247
248 try {
249 Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString());
250
251 Class<?>[] constructorParams = new Class<?>[earlierLines.size()];
252 Object[] constructorArgs = new Object[earlierLines.size()];
253
254 for (int i = 0; i < earlierLines.size(); ++i) {
255 constructorParams[i] = earlierLines.get(i).getScriptClass();
256 constructorArgs[i] = earlierLines.get(i).getScriptInstance();
257 }
258
259 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams);
260 Object scriptInstance;
261 try {
262 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
263 } catch (Throwable e) {
264 return LineResult.error(Throwables.getStackTraceAsString(e));
265 }
266 Field rvField = scriptClass.getDeclaredField("rv");
267 rvField.setAccessible(true);
268 Object rv = rvField.get(scriptInstance);
269
270 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType));
271
272 return LineResult.successful(rv, scriptDescriptor.getReturnType().equals(KotlinBuiltIns.getInstance().getUnitType()));
273 } catch (Throwable e) {
274 PrintWriter writer = new PrintWriter(System.err);
275 classLoader.dumpClasses(writer);
276 writer.flush();
277 throw ExceptionUtils.rethrow(e);
278 }
279 }
280
281 @Nullable
282 private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
283 WritableScope scope = new WritableScopeImpl(
284 JetScope.EMPTY, module,
285 new TraceBasedRedeclarationHandler(trace), "Root scope in analyzeNamespace");
286
287 scope.changeLockLevel(WritableScope.LockLevel.BOTH);
288
289 NamespaceDescriptorImpl rootNs = injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(FqName.ROOT);
290
291 // map "jet" namespace into KotlinBuiltIns
292 // @see DefaultModuleConfiguraiton#extendNamespaceScope
293 injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(KotlinBuiltIns.getInstance().getBuiltInsPackageFqName());
294
295 // Import a scope that contains all top-level namespaces that come from dependencies
296 // This makes the namespaces visible at all, does not import themselves
297 scope.importScope(rootNs.getMemberScope());
298
299 if (lastLineScope != null) {
300 scope.importScope(lastLineScope);
301 }
302
303 scope.changeLockLevel(WritableScope.LockLevel.READING);
304
305 // dummy builder is used because "root" is module descriptor,
306 // namespaces added to module explicitly in
307 injector.getTopDownAnalyzer().doProcess(scope, new NamespaceLikeBuilderDummy(), Collections.singletonList(psiFile));
308
309 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext(), messageCollector);
310 if (hasErrors) {
311 return null;
312 }
313
314 ScriptDescriptor scriptDescriptor = injector.getTopDownAnalysisContext().getScripts().get(psiFile.getScript());
315 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor);
316 if (lastLineScope == null) {
317 throw new IllegalStateException("last line scope is not initialized");
318 }
319
320 return scriptDescriptor;
321 }
322
323 public void dumpClasses(@NotNull PrintWriter out) {
324 classLoader.dumpClasses(out);
325 }
326
327 private static void registerEarlierScripts(
328 @NotNull GenerationState state,
329 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts
330 ) {
331 for (Pair<ScriptDescriptor, Type> t : earlierScripts) {
332 ScriptDescriptor earlierDescriptor = t.first;
333 Type earlierClassType = t.second;
334 registerClassNameForScript(state.getBindingTrace(), earlierDescriptor, earlierClassType);
335 }
336
337 List<ScriptDescriptor> earlierScriptDescriptors = Lists.newArrayList();
338 for (Pair<ScriptDescriptor, Type> t : earlierScripts) {
339 ScriptDescriptor earlierDescriptor = t.first;
340 earlierScriptDescriptors.add(earlierDescriptor);
341 }
342 state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors);
343 }
344
345 public static void compileScript(
346 @NotNull JetScript script,
347 @NotNull Type classType,
348 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts,
349 @NotNull GenerationState state,
350 @NotNull CompilationErrorHandler errorHandler
351 ) {
352 registerEarlierScripts(state, earlierScripts);
353 registerClassNameForScript(state.getBindingTrace(), script, classType);
354
355 state.beforeCompile();
356 KotlinCodegenFacade.generateNamespace(
357 state,
358 JetPsiUtil.getFQName((JetFile) script.getContainingFile()),
359 Collections.singleton((JetFile) script.getContainingFile()),
360 errorHandler);
361 }
362
363 }