Kinx ライブラリ - REPL
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は REPL です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
当初、REPL できないんじゃないか、と悲観していましたが、なんとなくそれっぽく動くものが出来上がったのでご紹介です。
というのも、evel
を素直に実行すると、1回実行するたびにコード領域が半端なく膨れ上がっていくので、以前紹介した Isolate と同じように別コンテキストで実行させるようにしました。そうすることで劇的にメモリ使用量が改善されました。
ところが、これだとコンテキストが別なので オブジェクトの受け渡しができない のです。これもクラス定義や関数定義を毎回 eval
に引き渡すことでなんとかやりくりするようにしました。その結果、、、
- 一応計算機程度には使えるものになった。
- 関数(
function
/native
)、クラス、モジュールもそれなりに使える。 - エスケープシーケンスで Windows でも カラフルに。
-
[TAB]
キーでのキーワード補完も頑張ってみた。 - East Asian Width のサポート。UTF8 文字も
[BS]
とか[DEL]
とかいけるはず。少なくとも日本語はOK。
という程度には完成しました。頑張った...
What is REPL?
「REPL ってなあに?」、=> Read-Eval-Print-Loop です。入力を読み取って eval()
(評価)して、その結果を表示する、というのを繰り返します。
使い方
起動方法
REPL は SpecTest と同じように Kinx 本体に組み込まれています。以下のように実行します。
$ ./kinx --exec:repl
kinx[ 0]> _
ここでは Markdown で記載しているので色が異なりますが、実際はプロンプトやキーワードなどがハイライトされ、カラー表示されます(Windows も Linux も)。
それを分かってもらうために最初は画像を張ってみたのですが、やはりデモだ、ということで、デモを頑張って作ってみました。誰か褒めてください。
オートコンプリート(キーワード補完)もちゃんと動いていることがわかります。この辺はデモとかが無いと理解が難しいですよね。百聞は一見に如かず。
サンプル
手始めに、ここ(20分ではじめるRuby) に載っているのを試してみましょう。よかった、この程度ならちゃんとできてる。
$ ./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]
キーを押すことで補完モードになります。補完モードでは、先行する文字列に対して続く可能性のある文字列を薄いグレーで補完します。割と頑張って作ってみた上記のデモで確認できます。といっても上まで戻るのは面倒でしょうから、もう一回載せておきます(せっかく作ったので)。
この候補が表示された状態でさらに [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
です。検索対象は以下の順に行われます。なので、標準ライブラリも参照できます。
- カレントディレクトリ
-
kinx
の実行ファイルのあるフォルダ配下のlib
フォルダ内 -
kinx
の実行ファイルのあるフォルダ配下のlib/std
フォルダ内 -
kinx
の実行ファイルのあるフォルダから見て../lib
フォルダ内 -
kinx
の実行ファイルのあるフォルダ配下のkinxlib
フォルダ内 -
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
ではまた、次回。