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