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