REPL(Read-eval-print loop)を作る。

REPL(Read-eval-print loop)を作る。

Kotlinソースのコードリーディングで、JLineというコンソール入出力ハンドリングライブラリを知りました。

JLine：http://jline.sourceforge.net/
Kotlin GitHub：https://github.com/JetBrains/kotlin

KotlinのREPLに使用されています。
JLineでやっていることをコーディングした記憶があり、ちょっとショック。
やはり、知らないという事は悪なのです。
今回はKotlinは使っていません。Javaで作りました。

とはいえ、REPLの構文解析／構文実行等は作っていません。言語仕様も決めていませんので。
起動、コマンド(Kotlin REPLの:〜)、構文入力受け付け的な所まで。
KotlinのREPLを真似してみました。

Kotlin REPL起動
![replkt1.png](https://qiita-image-store.s3.amazonaws.com/0/23378/a6655719-f556-a083-2308-2ff18d17db33.png)

Kotlin REPLコマンド実行
![replkt2.png](https://qiita-image-store.s3.amazonaws.com/0/23378/5fa4c86d-5f7e-12b7-6a93-a4f53b9d3ee2.png)

```java
package replbase;

import java.io.IOException;
import java.io.PrintWriter;
import jline.ConsoleReader;

public class REPLBase {

    private final String COMMAND = ":";
    private final String PROMPT_READLINE = ">>> ";
    private final String PROMPT_INCOMPLETE = "... ";
    private ConsoleReader consoleReader;

    enum STATUS {
        READLINE,
        INCOMPLETE,
        QUIT
    }
    private STATUS status = STATUS.READLINE;

    public static void main(String[] args) {
        REPLBase replBase = new REPLBase();
        replBase.start();
    }

    private void start() {
        try(PrintWriter printwriter = new PrintWriter(System.out);) {
            consoleReader = new ConsoleReader(System.in, printwriter);
            consoleReader.printString("Welcome to REPLBase.(JRE " + System.getProperty("java.runtime.version") + ")\n");
            consoleReader.printString("Type :help for help, :quit for quit\n");
            while(status != STATUS.QUIT) {
                String input = readLine();
                analyze(input);
            }
        }
        catch(IOException exception) {
            System.err.printf(exception.toString());
        }
    }

    private String readLine() throws IOException{
        String prompt = status == STATUS.READLINE ? PROMPT_READLINE : PROMPT_INCOMPLETE;
        String input = consoleReader.readLine(prompt);
        return input;
    }

    private void analyze(String input) throws IOException {
        if((status != STATUS.INCOMPLETE) &&
           (input.startsWith(COMMAND) == true)) {
            command(input);
        }
        else {
            compile(input);
        }
    }

    private void compile(String input) {
        if(input.endsWith("}") != true) {
            status = STATUS.INCOMPLETE;
        }
        else {
            status = STATUS.READLINE;
        }
    }

    private void command(String input) throws IOException {
        String command = input.replaceFirst(COMMAND, "");
        switch(command) {
            case "help":
                consoleReader.printString("Available commands:\n" +
                                             ":help    show this help\n" +
                                             ":quit    exit the interpreter\n");
                break;
            case "quit":
                status = STATUS.QUIT;
                break;
            default:
                consoleReader.printString("Unknown command\n" +
                                              "Type :help for help\n");
                break;
        }
    }

}
```

これをベースに拡張出来ます。
REPLBaseをベースクラスにすれば色々使えます。

start()メソッド内のConsoleReaderがJLineです。
readline()メソッドでプロンプト表示／入力待ち／入力ライン受け取りです。
受け取った結果をanalyze()内でコマンド処理(command())と構文解析以降処理(compile())に振り分けています。
command()メソッドを拡張すれば、コマンドを追加／変更出来ます。
compile()メソッドを拡張すれば、REPLの本体を実装出来ます。
analyze()／command()／compile()を派生側で実装出来るようにすることで拡張可能になります。