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 jline.console.ConsoleReader;
022 import jline.console.history.FileHistory;
023 import org.jetbrains.annotations.NotNull;
024 import org.jetbrains.kotlin.cli.common.KotlinVersion;
025 import org.jetbrains.kotlin.config.CompilerConfiguration;
026 import org.jetbrains.kotlin.utils.UtilsPackage;
027
028 import java.io.File;
029 import java.io.PrintWriter;
030 import java.util.Arrays;
031 import java.util.List;
032
033 public class ReplFromTerminal {
034
035 private ReplInterpreter replInterpreter;
036 private Throwable replInitializationFailed;
037 private final Object waitRepl = new Object();
038
039 private final ConsoleReader consoleReader;
040
041 public ReplFromTerminal(
042 @NotNull final Disposable disposable,
043 @NotNull final CompilerConfiguration compilerConfiguration) {
044 new Thread("initialize-repl") {
045 @Override
046 public void run() {
047 try {
048 replInterpreter = new ReplInterpreter(disposable, compilerConfiguration);
049 }
050 catch (Throwable e) {
051 replInitializationFailed = e;
052 }
053 synchronized (waitRepl) {
054 waitRepl.notifyAll();
055 }
056 }
057 }.start();
058
059 try {
060 consoleReader = new ConsoleReader("kotlin", System.in, System.out, null);
061 consoleReader.setHistoryEnabled(true);
062 consoleReader.setExpandEvents(false);
063 consoleReader.setHistory(new FileHistory(new File(new File(System.getProperty("user.home")), ".kotlin_history")));
064 }
065 catch (Exception e) {
066 throw UtilsPackage.rethrow(e);
067 }
068 }
069
070 private ReplInterpreter getReplInterpreter() {
071 if (replInterpreter != null) {
072 return replInterpreter;
073 }
074 synchronized (waitRepl) {
075 while (replInterpreter == null && replInitializationFailed == null) {
076 try {
077 waitRepl.wait();
078 }
079 catch (Throwable e) {
080 throw UtilsPackage.rethrow(e);
081 }
082 }
083 if (replInterpreter != null) {
084 return replInterpreter;
085 }
086 throw UtilsPackage.rethrow(replInitializationFailed);
087 }
088 }
089
090 private void doRun() {
091 try {
092 System.out.println("Welcome to Kotlin version " + KotlinVersion.VERSION +
093 " (JRE " + System.getProperty("java.runtime.version") + ")");
094 System.out.println("Type :help for help, :quit for quit");
095 WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE;
096 while (true) {
097 next = one(next);
098 if (next == WhatNextAfterOneLine.QUIT) {
099 break;
100 }
101 }
102 }
103 catch (Exception e) {
104 throw UtilsPackage.rethrow(e);
105 }
106 finally {
107 try {
108 ((FileHistory) consoleReader.getHistory()).flush();
109 }
110 catch (Exception e) {
111 System.err.println("failed to flush history: " + e);
112 }
113 }
114 }
115
116 private enum WhatNextAfterOneLine {
117 READ_LINE,
118 INCOMPLETE,
119 QUIT,
120 }
121
122 @NotNull
123 private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) {
124 try {
125 String line = consoleReader.readLine(next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> ");
126 if (line == null) {
127 return WhatNextAfterOneLine.QUIT;
128 }
129
130 if (line.startsWith(":") && (line.length() == 1 || line.charAt(1) != ':')) {
131 boolean notQuit = oneCommand(line.substring(1));
132 return notQuit ? WhatNextAfterOneLine.READ_LINE : WhatNextAfterOneLine.QUIT;
133 }
134
135 ReplInterpreter.LineResultType lineResultType = eval(line);
136 if (lineResultType == ReplInterpreter.LineResultType.INCOMPLETE) {
137 return WhatNextAfterOneLine.INCOMPLETE;
138 }
139 else {
140 return WhatNextAfterOneLine.READ_LINE;
141 }
142 }
143 catch (Exception e) {
144 throw UtilsPackage.rethrow(e);
145 }
146 }
147
148 @NotNull
149 private ReplInterpreter.LineResultType eval(@NotNull String line) {
150 ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line);
151 if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) {
152 if (!lineResult.isUnit()) {
153 System.out.println(lineResult.getValue());
154 }
155 }
156 else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) {
157 }
158 else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) {
159 System.out.print(lineResult.getErrorText());
160 }
161 else {
162 throw new IllegalStateException("unknown line result type: " + lineResult);
163 }
164 return lineResult.getType();
165 }
166
167 private boolean oneCommand(@NotNull String command) throws Exception {
168 List<String> split = splitCommand(command);
169 if (split.size() >= 1 && command.equals("help")) {
170 System.out.println("Available commands:");
171 System.out.println(":help show this help");
172 System.out.println(":quit exit the interpreter");
173 System.out.println(":dump bytecode dump classes to terminal");
174 System.out.println(":load <file> load script from specified file");
175 return true;
176 }
177 else if (split.size() >= 2 && split.get(0).equals("dump") && split.get(1).equals("bytecode")) {
178 getReplInterpreter().dumpClasses(new PrintWriter(System.out));
179 return true;
180 }
181 else if (split.size() >= 1 && split.get(0).equals("quit")) {
182 return false;
183 }
184 else if (split.size() >= 2 && split.get(0).equals("load")) {
185 String fileName = split.get(1);
186 String scriptText = FileUtil.loadFile(new File(fileName));
187 eval(scriptText);
188 return true;
189 }
190 else {
191 System.out.println("Unknown command");
192 System.out.println("Type :help for help");
193 return true;
194 }
195 }
196
197 private static List<String> splitCommand(@NotNull String command) {
198 return Arrays.asList(command.split(" "));
199 }
200
201 public static void run(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
202 new ReplFromTerminal(disposable, configuration).doRun();
203 }
204
205 }