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    }