001 /*
002 * Copyright 2010-2015 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.kotlin.cli.jvm.repl;
018
019 import com.intellij.openapi.Disposable;
020 import com.intellij.openapi.util.io.FileUtil;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.kotlin.cli.common.KotlinVersion;
023 import org.jetbrains.kotlin.cli.jvm.repl.messages.*;
024 import org.jetbrains.kotlin.cli.jvm.repl.reader.ConsoleReplCommandReader;
025 import org.jetbrains.kotlin.cli.jvm.repl.reader.IdeReplCommandReader;
026 import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandReader;
027 import org.jetbrains.kotlin.config.CompilerConfiguration;
028 import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
029
030 import java.io.File;
031 import java.io.PrintWriter;
032 import java.util.Arrays;
033 import java.util.List;
034
035 public class ReplFromTerminal {
036
037 private ReplInterpreter replInterpreter;
038 private Throwable replInitializationFailed;
039 private final Object waitRepl = new Object();
040
041 private final boolean ideMode;
042 private ReplSystemInWrapper replReader;
043 private final ReplWriter replWriter;
044 private final ReplErrorLogger replErrorLogger;
045
046 private ReplCommandReader commandReader;
047
048 public ReplFromTerminal(
049 @NotNull final Disposable disposable,
050 @NotNull final CompilerConfiguration compilerConfiguration
051 ) {
052 String replIdeMode = System.getProperty("kotlin.repl.ideMode");
053 ideMode = replIdeMode != null && replIdeMode.equals("true");
054
055 // wrapper for `out` is required to escape every input in [ideMode];
056 // if [ideMode == false] then just redirects all input to [System.out]
057 // if user calls [System.setOut(...)] then undefined behaviour
058 if (ideMode) {
059 ReplSystemOutWrapperForIde soutWrapper = new ReplSystemOutWrapperForIde(System.out);
060 replWriter = soutWrapper;
061 System.setOut(soutWrapper);
062 }
063 else {
064 replWriter = new ReplConsoleWriter();
065 }
066
067 // wrapper for `in` is required to give user possibility of calling
068 // [readLine] from ide-console repl
069 if (ideMode) {
070 replReader = new ReplSystemInWrapper(System.in, replWriter);
071 System.setIn(replReader);
072 }
073
074 replErrorLogger = new ReplErrorLogger(ideMode, replWriter);
075
076 new Thread("initialize-repl") {
077 @Override
078 public void run() {
079 try {
080 replInterpreter = new ReplInterpreter(disposable, compilerConfiguration, ideMode, replReader);
081 }
082 catch (Throwable e) {
083 replInitializationFailed = e;
084 }
085 synchronized (waitRepl) {
086 waitRepl.notifyAll();
087 }
088 }
089 }.start();
090
091 try {
092 commandReader = createCommandReader();
093 }
094 catch (Exception e) {
095 replErrorLogger.logException(e);
096 }
097 }
098
099 @NotNull
100 private ReplCommandReader createCommandReader() {
101 return ideMode ? new IdeReplCommandReader()
102 : new ConsoleReplCommandReader();
103 }
104
105 private ReplInterpreter getReplInterpreter() {
106 if (replInterpreter != null) {
107 return replInterpreter;
108 }
109 synchronized (waitRepl) {
110 while (replInterpreter == null && replInitializationFailed == null) {
111 try {
112 waitRepl.wait();
113 }
114 catch (Throwable e) {
115 throw ExceptionUtilsKt.rethrow(e);
116 }
117 }
118 if (replInterpreter != null) {
119 return replInterpreter;
120 }
121 throw ExceptionUtilsKt.rethrow(replInitializationFailed);
122 }
123 }
124
125 private void doRun() {
126 try {
127 replWriter.printlnWelcomeMessage("Welcome to Kotlin version " + KotlinVersion.VERSION +
128 " (JRE " + System.getProperty("java.runtime.version") + ")");
129 replWriter.printlnWelcomeMessage("Type :help for help, :quit for quit");
130 WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE;
131 while (true) {
132 next = one(next);
133 if (next == WhatNextAfterOneLine.QUIT) {
134 break;
135 }
136 }
137 }
138 catch (Exception e) {
139 replErrorLogger.logException(e);
140 }
141 finally {
142 try {
143 commandReader.flushHistory();
144 }
145 catch (Exception e) {
146 replErrorLogger.logException(e);
147 }
148 }
149 }
150
151 public enum WhatNextAfterOneLine {
152 READ_LINE,
153 INCOMPLETE,
154 QUIT,
155 }
156
157 @NotNull
158 private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) {
159 try {
160 String line = commandReader.readLine(next);
161
162 if (line == null) {
163 return WhatNextAfterOneLine.QUIT;
164 }
165
166 line = UnescapeUtilsKt.unescapeLineBreaks(line);
167
168 if (line.startsWith(":") && (line.length() == 1 || line.charAt(1) != ':')) {
169 boolean notQuit = oneCommand(line.substring(1));
170 return notQuit ? WhatNextAfterOneLine.READ_LINE : WhatNextAfterOneLine.QUIT;
171 }
172
173 ReplInterpreter.LineResultType lineResultType = eval(line);
174 if (lineResultType == ReplInterpreter.LineResultType.INCOMPLETE) {
175 return WhatNextAfterOneLine.INCOMPLETE;
176 }
177 else {
178 return WhatNextAfterOneLine.READ_LINE;
179 }
180 }
181 catch (Exception e) {
182 throw ExceptionUtilsKt.rethrow(e);
183 }
184 }
185
186 @NotNull
187 private ReplInterpreter.LineResultType eval(@NotNull String line) {
188 ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line);
189 if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) {
190 replWriter.notifyCommandSuccess();
191 if (!lineResult.isUnit()) {
192 replWriter.outputCommandResult(lineResult.getValue());
193 }
194 }
195 else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) {
196 replWriter.notifyIncomplete();
197 }
198 else if (lineResult.getType() == ReplInterpreter.LineResultType.COMPILE_ERROR) {
199 replWriter.outputCompileError(lineResult.getErrorText());
200 }
201 else if (lineResult.getType() == ReplInterpreter.LineResultType.RUNTIME_ERROR) {
202 replWriter.outputRuntimeError(lineResult.getErrorText());
203 }
204 else {
205 throw new IllegalStateException("unknown line result type: " + lineResult);
206 }
207 return lineResult.getType();
208 }
209
210 private boolean oneCommand(@NotNull String command) throws Exception {
211 List<String> split = splitCommand(command);
212 if (split.size() >= 1 && command.equals("help")) {
213 replWriter.printlnHelpMessage("Available commands:\n" +
214 ":help show this help\n" +
215 ":quit exit the interpreter\n" +
216 ":dump bytecode dump classes to terminal\n" +
217 ":load <file> load script from specified file"
218 );
219 return true;
220 }
221 else if (split.size() >= 2 && split.get(0).equals("dump") && split.get(1).equals("bytecode")) {
222 getReplInterpreter().dumpClasses(new PrintWriter(System.out));
223 return true;
224 }
225 else if (split.size() >= 1 && split.get(0).equals("quit")) {
226 return false;
227 }
228 else if (split.size() >= 2 && split.get(0).equals("load")) {
229 String fileName = split.get(1);
230 String scriptText = FileUtil.loadFile(new File(fileName));
231 eval(scriptText);
232 return true;
233 }
234 else {
235 replWriter.printlnHelpMessage("Unknown command\n" +
236 "Type :help for help"
237 );
238 return true;
239 }
240 }
241
242 private static List<String> splitCommand(@NotNull String command) {
243 return Arrays.asList(command.split(" "));
244 }
245
246 public static void run(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
247 new ReplFromTerminal(disposable, configuration).doRun();
248 }
249
250 }