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