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