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