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    }