1
0

More than 3 years have passed since last update.

Kinx ライブラリ - REPL

Posted at

Kinx ライブラリ - REPL

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は REPL です。

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

というのも、evel を素直に実行すると、1回実行するたびにコード領域が半端なく膨れ上がっていくので、以前紹介した Isolate と同じように別コンテキストで実行させるようにしました。そうすることで劇的にメモリ使用量が改善されました。

ところが、これだとコンテキストが別なので オブジェクトの受け渡しができない のです。これもクラス定義や関数定義を毎回 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 と同じように Kinx 本体に組み込まれています。以下のように実行します。

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

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

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

repl.gif

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

サンプル

手始めに、ここ(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] 入力後の編集はできません。

この機能を使って、functionclassmodule などを定義します。

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

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

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

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

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

場合によってはうまく機能しないかもしれませんが、文字列を途中まで入力して [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 が無いと半人前、みたいな意見をどこかで読んだので、頑張って作ってみました(大変だった...)。とは言っても、まだまだ発展途上。これを初版として、要望があれば改善対応していきたいと思います。

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

ではまた、次回。

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0