Kinx ライブラリ - REPL

# Kinx ライブラリ - REPL

## はじめに

**「見た目は JavaScript、頭脳(中身)は Ruby、（安定感は AC/DC）」** でお届けしているスクリプト言語 [Kinx](https://github.com/Kray-G/kinx)。言語はライブラリが命。ということでライブラリの使い方編。 

今回は REPL です。

* 参考
    * 最初の動機 ... [スクリプト言語 KINX（ご紹介）](https://qiita.com/Kray-G/items/ca08b6fb40d15dd0ec76)
        * 個別記事へのリンクは全てここに集約してあります。
    * リポジトリ ... [https://github.com/Kray-G/kinx](https://github.com/Kray-G/kinx)
        * Pull Request 等お待ちしております。 

当初、REPL できないんじゃないか、と悲観していましたが、なんとなくそれっぽく動くものが出来上がったのでご紹介です。

というのも、`evel` を素直に実行すると、1回実行するたびにコード領域が半端なく膨れ上がっていくので、以前紹介した [Isolate](https://qiita.com/Kray-G/items/78cf553b65e286ea39c4) と同じように別コンテキストで実行させるようにしました。そうすることで劇的にメモリ使用量が改善されました。

ところが、これだとコンテキストが別なので **オブジェクトの受け渡しができない** のです。これもクラス定義や関数定義を毎回 `eval` に引き渡すことでなんとかやりくりするようにしました。その結果、、、

1. 一応計算機程度には使えるものになった。
2. 関数(`function`/`native`)、クラス、モジュールもそれなりに使える。
3. エスケープシーケンスで **Windows でも** カラフルに。
4. `[TAB]` キーでのキーワード補完も頑張ってみた。
5. East Asian Width のサポート。UTF8 文字も `[BS]` とか `[DEL]` とかいけるはず。少なくとも日本語はOK。

という程度には完成しました。頑張った...

## What is REPL?

「REPL ってなあに?」、=> Read-Eval-Print-Loop です。入力を読み取って `eval()`（評価）して、その結果を表示する、というのを繰り返します。

## 使い方

### 起動方法

REPL は [SpecTest](https://qiita.com/Kray-G/items/ba7422568ab50fa18414) と同じように Kinx 本体に組み込まれています。以下のように実行します。

```
$ ./kinx --exec:repl
kinx[  0]> _
```

ここでは Markdown で記載しているので色が異なりますが、実際はプロンプトやキーワードなどがハイライトされ、カラー表示されます（Windows も Linux も）。

それを分かってもらうために最初は画像を張ってみたのですが、やはりデモだ、ということで、デモを頑張って作ってみました。**誰か褒めてください**。

![repl.gif](https://raw.githubusercontent.com/Kray-G/kinx/master/doc/utility/repl.gif)

オートコンプリート（キーワード補完）もちゃんと動いていることがわかります。この辺はデモとかが無いと理解が難しいですよね。百聞は一見に如かず。

### サンプル

手始めに、[ここ(20分ではじめるRuby)](https://www.ruby-lang.org/ja/documentation/quickstart/) に載っているのを試してみましょう。よかった、この程度ならちゃんとできてる。

```
$ ./kinx --exec:repl
kinx[  0]> "Hello, world"
=> "Hello, world"

kinx[  1]> System.println("Hello, world")
Hello, world
=> (null)

kinx[  2]> 3+2
=> 5

kinx[  3]> 3*2
=> 6

kinx[  4]> 3**2
=> 9

kinx[  5]> Math.sqrt(9)
=> 3

kinx[  6]> a = 3 ** 2
=> 9

kinx[  7]> b = 4 ** 2
=> 16

kinx[  8]> Math.sqrt(a+b)
=> 5
```

では本題です。

### コマンド一覧

コマンドは全て `.` で始まります。

|       コマンド        |                                     内容                                      |
| --------------------- | ----------------------------------------------------------------------------- |
| `.help`               | ヘルプの表示                                                                |
| `.quit`               | 終了                                                                          |
| `.history`            | コマンド履歴の表示                                                            |
| `.vars`               | 現在の変数一覧表示                                                            |
| `.delete name`        | 現在の変数一覧から `name` の変数を削除                                        |
| `.showdef name`       | 関数やクラスの定義を表示                                                      |
| `.cursor [*on/off]`   | カーソル表示の ON/OFF（デフォルト ON）                                        |
| `.fullcode [on/*off]` | フル入力モード（デフォルト OFF）、`.run` コマンドが入力されるまでは実行しない |
| `.time [on/*off]`     | 実行時間計測モード（デフォルト OFF）、実行後に実行時間を表示                  |
| `.run`                | フル入力モード時に実行させるために使用                                        |

尚、`Ctrl-C` でも終了してしまいますので、その点はご了承ください。

`.help` と打つと、以下のヘルプが表示されます。

```
kinx[  0]> .help
Kinx REPL Command: * means by default
    .help               Display this help.
    .quit               Quit REPL.
    .history            Display command history.
    .vars               Display variables with its value.
    .delete name        Delete a variable by name.
    .showdef name       Display function/class definition by name.
    .cursor [*on|off]   Set to 'off' to make the cursor invisible.
    .fullcode [on|*off] Set to 'on', and the code will be executed by .run instead of immediately.
    .time [on|*off]     Set to 'on' to measure and display elapsed time.
    .run                Execute the code only with .fullcode 1.

REPL Operation:
    [^] Arrow up        Choose a previous command.
    [v] Arrow down      Choose a next command.
    [<] Arrow left      Move cursor to left.
    [>] Arrow right     Move cursor to right.
    Ctrl+[<]            Move cursor to left by word.
    Ctrl+[>]            Move cursor to right by word.
    [DEL]               Delete character on cursor.
    [BS]                Delete previous character.
    [TAB]               Move to the next tab stop, or auto-complete.
```

カーソルキーの左右でカーソル移動、Ctrl+左右で単語ごとに移動。上下キーで履歴を参照することができます。

### ラインエディタ

基本的にラインエディタで入力を受け付け、入力したものを即時に実行します。

```
kinx[  0]> 10 * 2
=> 20
```

### 自動マルチライン判定と自動インデント

`{` と対応する `}` を自動的に認識して、全体のブロックが入力し終わるまでエディタモードを継続します。この時、`{` の数によって自動的に空白 4 つ分のインデントを付加します。

ただし、編集できるのはその行だけなので、`[Enter]` 入力後の編集はできません。

この機能を使って、`function`、`class`、`module` などを定義します。

```
kinx[  0]> function func(...a) {
	[  1]>     return a.reduce(&(r, e) => r + e);
	[  2]> }
=> function func
```

こうしておくことで、次のように関数を使えるようになります。

```
kinx[  0]> function func(...a) {
    [  1]>     return a.reduce(&(r, e) => r + e);
    [  2]> }
=> function func

kinx[  3]> func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
=> 55
```

登録した関数は以下のように確認できます。

```
kinx[  4]> .vars
function func(...a)

kinx[  5]> .showdef func
function func(...a) {
    return a.reduce(&(r, e) => r + e);
}
```

### キーワード補完

コマンドやキーワードは `[TAB]` キーを押すことで補完モードになります。補完モードでは、先行する文字列に対して続く可能性のある文字列を薄いグレーで補完します。割と頑張って作ってみた上記のデモで確認できます。といっても上まで戻るのは面倒でしょうから、もう一回載せておきます（せっかく作ったので）。

![repl.gif](https://raw.githubusercontent.com/Kray-G/kinx/master/doc/utility/repl.gif)

この候補が表示された状態でさらに `[TAB]` キーを押すと、次の候補に切り替わります。この時、以下のキー操作になります。

* 文字の入力 ... 一旦補完モードがキャンセルされ、先行する文字に入力した文字が追記されます。
* 空白や記号の入力 ... 現在の候補で確定し、入力された文字が追記されます。
* `[BS]`、`[DEL]` キー ... 現在の候補で確定し、カーソルはその位置で入力待ち状態になります。
* `[Enter]` キー ... その場で確定させて、実行または次の行に移動させます。

補完はその位置のコンテキストで候補が変わります。例えば、過去の履歴から配列と推定される場合は `Array` のメソッドなども候補に追加されます。また、過去の履歴で `new` 演算子でインスタンス化したオブジェクトを代入した場合など、そのクラスメソッドなども候補に追加されます。

この実行例は最初に見せたデモで一部確認することが可能です。例えば 最後の行の `s.` の候補として `value` と `sum` が表示されるようになっています。

場合によってはうまく機能しないかもしれませんが、文字列を途中まで入力して `[TAB]` キーを押すことでその文字列で始まる候補が順々に出てくるので、いろいろと試してみてください。

あと、`namespace` は未対応です。

### 変数一覧

`.vars` コマンドで現時点で有効になっている変数の一覧が表示されます。尚、関数名やクラス名も変数名と同列の扱いなので、同じ名前で登録することはできません。後から定義したもので上書きされます。

```
kinx[ 20]> .vars
class Sample(...a)
a = 10
b = 20
c = 30
d = 40
e = 50
f = 60
g = 70
sample = new Sample(a, b, c, d, e, f, g)
```

### 変数の削除

`.delete` コマンドで不要になった変数を指定して削除できます。同じ変数名に代入すると上書きされるので、あまり使う機会はないかもしれません。ただ、上書きを繰り返して変数の登録順序がおかしくなったり、`.delete` コマンドを使ったことで変数の依存関係がおかしくなった場合に `Compile Error` となるケースもあります。そういったときにおかしくなった変数を削除し、新たに登録しなおすことで正しく動作させられるようになります。

例えば、上記の変数が登録された状態で以下のようになるケースがあります。

```
kinx[ 25]> .delete g      // g を削除
kinx[ 26]> sample.sum()   // g が無くなったので、sample インスタンスが不正な形に。
=> Error: Compile Error.

kinx[ 27]> .vars
class Sample(...a)
a = 10
b = 20
c = 30
d = 40
e = 50
f = 60
sample = new Sample(a, b, c, d, e, f, g)  // <- ここの g が無くなっているので不正な形。

kinx[ 28]> g = 60         // 新たに g を登録したいがコンパイルエラー。
=> Error: Compile Error.

kinx[ 29> .delete sample  // 不正な形のインスタンスが実行の邪魔をしているので、それも削除。
kinx[ 30]> g = 60         // g を再登録可能に。
=> 60

kinx[ 31]> var sample = new Sample(a, b, c, d, e, f, g)  // sample も再登録。
=> (null)

kinx[ 32]> sample.sum() // 新たな g を使って実行が可能に。
=> 270
```

### 関数、クラス、モジュール定義の表示

`.vars` では一覧表示の中に関数名やクラス名は表示されますが、その定義がどうだったかを表示はしません。そこで `.showdef` コマンドを使うと定義の中身を参照することができます。

```
kinx[ 33]> .showdef Sample
class Sample(...a) {
    @value = a;
    public sum() {
        return @value.reduce(&(r, e) => r + e);
    }
}
```

### 実行履歴

これまでの実行履歴を `.history` コマンドで確認することができます。例えば以下のような表示がされます（上記例とは番号が異なっていますが、サンプルです）。

	[  0]: class Sample(...a) {
	[  1]:     @value = a;
	[  2]:     public sum() {
	[  3]:         return @value.reduce(&(r, e) => r + e);
	[  4]:     }
	[  5]: }
	[  6]: a = new Sample(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
	[  7]: .vars
	[  8]: System.println(a.sum());
	[  9]: a = 10
	[ 10]: b = 20
	[ 11]: c = 30
	[ 12]: d = 40
	[ 13]: e = 50
	[ 14]: f = 60
	[ 15]: g = 70
	[ 16]: .vars
	[ 17]: var sample = new Sample(a, b, c, d, e, f, g)
	[ 18]: sample.sum()
	[ 19]: .vars
	[ 20]: sample.sum()
	[ 21]: .showdef System
	[ 22]: .showdef Sample
	[ 23]: .vars
	[ 24]: sample.sum()
	[ 25]: .delete g
	[ 26]: sample.sum()
	[ 27]: .vars
	[ 28]: g = 60
	[ 29]: g = 60
	[ 30]: .vars
	[ 31]: .delete sample
	[ 32]: g = 60
	[ 33]: var sample = new Sample(a, b, c, d, e, f, g)
	[ 34]: sample.sum()
	[ 35]: .history


過去の履歴から、以下のように `!` を使って参照実行させることもできます。

```
kinx[ 36]> !34
=> "sample.sum();"
=> 270
```

また、カーソルキーの上下で履歴を再入力対象として選択することも可能です。

### 実行時間計測

`.time on` とすると、実行結果を表示するようになります。計測結果は単純に `eval` して返ってくるまでの経過時間なので色々なオーバーヘッドも含まれますが、目安にはなるでしょう。

```
kinx[  0]> native fib(n) {
    [  1]>     return n if (n < 3);
    [  2]>     return fib(n-2) + fib(n-1);
    [  3]> }
=> native<int> fib

kinx[  4]> .time on
.time: on

kinx[  5]> fib(39)
=> 102334155

elapsed:    1.238 s

kinx[  6]> fib(34)
=> 9227465

elapsed:    0.131 s
```

### フル入力モード

`.fullcode on` とすると、`[Enter]` キーを押しただけでは実行しません。`.run` コマンドを入力するまでコマンド入力を継続します。ただし、今のところフル入力モードでは変数の登録やクラス定義の登録などが **されません**。全てその入力だけの単発の実行になります。

```
kinx[  0]> .fullcode on
.fullcode: on

kinx[  1]> class Something() {
    [  2]>         public println(...a) {
    [  3]>                 System.println(a);
    [  4]>         }
    [  5]> }
    [  6]> s = new Something();
    [  7]> s.println(1, 2, 3, 4, 5, 6, 7, 8);
    [  8]> .run
[1, 2, 3, 4, 5, 6, 7, 8]
=> {"s":null}

kinx[  9]> .vars

kinx[ 10]>
```

### 外部ファイル読み込み

外部ファイルを取り込むには `.load` コマンドを使います。通常ライブラリとして `using` するモジュールも `.load` で読み込みます。`.load` で読み込むことによって、ある程度のメソッド名補完を有効にすることができます。

例えば、`DateTime` を使う場合は以下のようにできます。

```
kinx[  0]> .load DateTime
=> Successfully loaded.

kinx[  1]> var dt = new DateTime(2020, 1, 1, 9, 30, 0)
=> (null)

kinx[  2]> .vars
dt = new DateTime(2020, 1, 1, 9, 30, 00)

kinx[  3]> dt.weekday()
=> 3

kinx[  4]> System.println(dt)
2020/01/01 09:30:00
```

検索ファイル名は `指定名.kx` です。検索対象は以下の順に行われます。なので、標準ライブラリも参照できます。

1. カレントディレクトリ
2. `kinx` の実行ファイルのあるフォルダ配下の `lib` フォルダ内
3. `kinx` の実行ファイルのあるフォルダ配下の `lib/std` フォルダ内
4. `kinx` の実行ファイルのあるフォルダから見て `../lib` フォルダ内
5. `kinx` の実行ファイルのあるフォルダ配下の `kinxlib` フォルダ内
6. `kinx` の実行ファイルのあるフォルダ配下の `kinxlib/std` フォルダ内

> すみません、v0.9.2 リリースでは考慮漏れ（5 と 6 が Linux 用に必要だったが未実装なの）で Linux では `.load DateTime` がエラーします。ワークアラウンドとして `.load /usr/bin/kinxlib/std/DateTime` を指定してください。大変ですが。すみません。

ただし、DateTime に関しては現在 2 つほど注意点があります。将来のバージョンでは改善するかもしれません。

* `new` 演算子を使わなくても DateTime オブジェクトを作れますが、メソッド名補完をするには `new` 演算子を使う必要があります。
* `new DateTime()` で現在時刻を取得しますが、実行ごとに新たに `new` されるため、実行ごとに取得する値（時刻）が変化します。

### East Asian Width

UTF8 での入力をサポートしています。East Asian Width の定義に従ってカーソル移動を行っているので、全角幅の文字はその幅で移動します。
以下の例では、`あいうえお` でも全角幅で 1 文字ずつ移動し、`ｱｲｳｴｵ` では半角幅でカーソルが移動します。また、`[DEL]` キーや `[BS]` キーでの削除も正しく動作します。

```
$ ./kinx --exec:repl
kinx[  0]> a = "あいうえおｱｲｳｴｵ";
=> "あいうえおｱｲｳｴｵ"

kinx[  1]> System.println(a);
あいうえおｱｲｳｴｵ
=> (null)

kinx[  2]> a = "あいうおｱｲｴｵ";  // 「ｳ」の上で [DEL] し、「お」の上で [BS] を押下。
=> "あいうおｱｲｴｵ"

kinx[  3]> System.println(a);
あいうおｱｲｴｵ
=> (null)
```

## おわりに

なんかプログラミング言語は **REPL が無いと半人前**、みたいな意見をどこかで読んだので、頑張って作ってみました（大変だった...）。とは言っても、まだまだ発展途上。これを初版として、要望があれば改善対応していきたいと思います。

これを機会に触ってくれる人が増えると嬉しいかも。リリースビルドも公開しているので、もし宜しければ。

* リポジトリ ... https://github.com/Kray-G/kinx
	* リリース ... https://github.com/Kray-G/kinx/releases

ではまた、次回。