ぶていのログでぶログ

思い出したが吉日

reddish-shell v0.8.0 開発進捗 / 複数行サポート,バグ修正,コマンド補完,コマンド置換,while/until文の追加

前回から間が空いてしまったが久しぶりのバージョンアップ。 間が空いてしまったのは、あまりいい実装案が思いつかなかったのが原因。ooo このままだ、ズルズル開発が止まってしまうと思ったので、一旦実装したいやつを後回しにしてできるところから作っていった。

commit一覧 * c32e239 Add the ability to omit then in unless as well. * 65e506c Support multi line * 202ba4e Respect empty argument. * 30cfde5 Support script file * 5d18bec Fix ENV with multiple types mixed in. * 61ebe1a Fix escaped env word. * 9aef6ff Add commandline completion * f2a6e6d Change :dquote to :normal. * 3dff2ba Add command substitution * 512a1aa Add while statement * e9fd60c Add until statement * 90819c8 v0.8.0

unless文でthenを省略できるようにした

前回の記事で書いたとおりunlessでthenを省略できるようにするのをわすれていたので実装した。

複数行サポート

コマンドラインで複数行を扱えるようにした。 クォートを閉じていなかったり、\で終わっている場合は次の行の入力を促される。

f:id:buty4649:20210214171659p:plain
複数行サポート

複数行をサポートをしたのはいいが、コマンドラインの制御に使っているlinenoiseが複数行を扱うことを想定していないため、ヒストリー機能が1行ごとの保存になっている。。 今の所どうしようもないので諦め。

また、複数行をサポートしたことでスクリプトファイルを扱えるようにした。 シェバンにreddishを指定すれば実行できる。

❯ cat example.rsh
#!./mruby/bin/reddish

echo OK

if true
  echo true
end
❯ ./example.rsh
OK
true

バグ修正

開発しているときにいろいろバグを見つけたので修正した。

まず、空の変数を引数に指定した場合、無視されるというバグを直した。 例えば、 TEST=""; [ -n $TEST ] && echo true とした場合、ここでは何も表示されないのが正しい挙動なのだが、trueと表示されるというバグがあった。 これはなぜかというと、修正前は引数の変数などをパースしたあとに空の文字列があったらArrayから排除していた。 しかし、こうすると本来であれば test -n "" というコマンドが test -n というコマンドになってしまうのであった。 空の引数にも意味があるのだ…。

次のバグは、環境変数に設定する文字列がクォートなどの文字種が混ざっている場合正しく認識されなかった問題。 例えば、 FOO=test"test1" としたときに、 $FOO には test しか代入されず、test1というコマンドを実行するようになっていたので修正した。

最後は文字列中の$がエスケープできないというもの。こちらもシュッと直した。

コマンド補完機能

linenoiseの機能を使ってコマンドの補完をできるようにした。 ただし、あんまりリッチな機能はないので、TABを押すたびにインクリメントで変わっていくだけで、候補を一覧で出すとかhistoryから使用頻度を見て候補順を最適化するみたいな機能はない…。 まぁ、ここらへんはhistoryとかと合わせてコア機能が完成したあとにターミナルの改善を行いたいと思っているのでそれまで放置。

コマンド置換

コマンドの実行結果をコマンドラインとして扱える機能。 いわゆる $(command) とかバッククォート(`)でくくるとその中身が、コマンドとして扱われて出力を利用できる。 whileやuntilを実装するときに、例えば10までカウントするみたいなのを作るときにこの機能がないとつらいので先に実装した。 そして、こいつの実装が冒頭で書いたあまりいい実装案が思いつかなかった機能の引き金になった…

while/untilの実装

if文を実装してたのでwhile/unitlの実装は一瞬でできた。 しかし、実装は単純なのだがbashに比べると約10倍遅い・・・なんでだ。

❯ /usr/bin/time ./mruby/bin/reddish -c 'TEST=0; while [ $TEST -ne 1000 ]; do TEST=`expr $TEST + 1`;done'
3.89user 0.47system 0:04.20elapsed 103%CPU (0avgtext+0avgdata 5760maxresident)k
0inputs+0outputs (0major+885919minor)pagefaults 0swaps
❯ /usr/bin/time bash -c 'TEST=0; while [ $TEST -ne 1000 ]; do TEST=`expr $TEST + 1`;done'
0.40user 0.10system 0:00.48elapsed 105%CPU (0avgtext+0avgdata 3268maxresident)k
0inputs+0outputs (0major+141812minor)pagefaults 0swaps

Process.forkが遅いとかあるのだろうか。。。 とりあえず、今はこのパフォーマンスの悪さがクリティカルなわけではないので放置…そのうち改善したい。

悩み:ネストした文字列のパース

今かなり実装に悩んでいるものがある。 それは、Wordトークンのパースをどうするかというものだ。 具体的にどういうことかというと、コマンドや引数などは、lexerにおいてはWordというトークンに分割する。 これに関しては前に記事に書いたのでそちらを参照してほしい(今の実装とはちょっと違うけど)。

lexerでWordトークンをパースしたときに付加情報として、普通のテキスト(type=:normal)かクォートされたもの(type=:quote)なのかコマンド置換(type=:execute)なのかをつけている。 これをみて、最終的にexecutorがこのWordをどう処理するか決まる。

しかし、今回のアップデートでコマンド置換が増えたことでWordトークンがネストできるようになってしまった。 例えば、 "$(echo OK)" みたいにダブルクォートでくくられた中にコマンド置換を入れることができる。 今のreddishではこれらは $(echo OK)を保持するtype=:normalなWordとなる。 ここで悩ましいのが、この文字列をパースするのがexecutorにも必要になるということだ。 lexerにも $(〜) をパースする機構があるのに、executorにも必要になってしまう・・・。 そして、reddishにおいてはlexerはmgemになっていてexecutorのある本体と分離しているので、どちらにも定義しないといけないという状態になっている…。

これを回避するために、1. 諦めてどちらにも実装する or 2. lexer側でネストしたWordも含めてパースする のどちらかなぁっと考えている。 2の方法で実装しようとしたところ、かなり大規模な改修になりかつ私のスキルだとぐちゃぐちゃなコードになってしまったので、開発が止まったのであった。。。

最終的に、この問題を棚上げして実装できるところからやることにした。

次回の実装

上記のネストした文字列のパースをどうにかするか、for文を実装するか。 あと、break/continueキーワードの実装あたりをやりたい。 break/continueキーワードは、どう実装したら今のreddishに組み込めるか全くbashの実装とかみつつ、もしかしたら、根本的にガッツリ見直す必要があるかもしれない。