ぶていのログでぶログ

思い出したが吉日

reddish-shell v0.6.0 開発進捗 | parser/lexerの見直しとif文/unless文の追加

期限のあるなにかに迫られていると、別のことが捗るわけでreddish-shell v0.6.0になった。 一区切りついたので、もう1つの進捗も上がるだろうたぶん・・・

commit一覧 * f4d5f0a Improve parser * 69a3c3c Improve lexer * 33e0a64 Add if statement * ced3806 Add unless statement * bb69d5c Fix syntax error for blank word * 29dbc04 Fix the error that occurs immediately after startup. * 7fa4deb Changed to take $IFS as an argument. * 27d8756 Add support for redirection if statements. * 37bcce5 Add return the last status to if statement. * bbd05b3 v0.6.0

parserとlexerのブラッシュアップ

いままでゴリゴリとコードを書いてきたのだけど、if文を追加するにあたってparserやlexerに手をいれたところひたすらめんどくさい感じになったのでこの際一気に書き直した。

parserでは、ReddishParser::Actionを継承したクラスを使う側が用意する必要があったのだが、これを完全にやめてparser側で完結するようにした。 なぜこのような仕組みにしていたかというと、reddish-shell以外への利用を想定したものだったのだが、そんな使われるかわからない未来に対してなぞの拡張性を保ったがために複雑になってしまったのでこれをやめた。 また、一部構文がおかしいところがあったのでこれを直した。 例えば、 echo > foo bar というコマンドラインがあったとして、bashではechoにbarという引数を渡して、fooに標準出力を書き込むような動作になる。 しかしながら、reddish-shellでは、foo bar というファイルに標準出力を書き込むようになっていたのでbashに合わせるようにした。 この変更によって > foo echo bar みたいなコマンドも許容することになったのだが、これを許容するかは少し悩んだ。 悩んだ理由なのだが、fishではこの構文はNGになる。 最初にくる文字列がコマンドであると確定するので、サジェストがしやすくなるからだと思うし、そもそも、こんな変な書き方はまずしないだろうというのが根拠だ。 とはいえ、reddish-shellはある程度のbash互換を目指しているのでこの構文を許容するようにした。 不具合が生じたら直せばいいしな。

lexerは正規表現を使って書くことでかなりスマートになったんじゃないかなぁと思う。 また、String#slice!(Regexp) という正規表現にマッチした部分を返すメソッドに気がついたのも大きいと思う。 今後新しいキーワードが増えても簡単に追加できるようになったので満足。

if文/unless文の追加

ついに待望の制御分の追加を行った。 とはいえ、前述のparser/lexerのブラッシュでシュッと追加できるようになったのでかなり簡単に実装できた。 また、パースしたコマンドラインを実行するクラス(Reddish::Executor)も我ながらよくできていて、数行足すだけで意図した挙動を作れたのはかなり嬉しいし楽しい。 今まで積み上げてきたものがうまくハマってくると、モノづくりはめちゃくちゃ楽しくてとてもよい。

追加したif文だが、以下のようなbashの構文と同様なものにした。

if condition; then command; fi
if condition; then command; else command; fi
if condition; then command; elif condition; then command fi

そして、Ruby構文のようなif文もつかえるようにした

if condition; then command; end
if condition; then command; elsif condition; end

fi ってなんだよ!!!ってなってた人もこれでニッコリするだろう!(多分 しかしながら、私の今の技術では then をなくすことができなかったのが悔やまれる…。 なぜ、thenを消せないかというとRubyでは ; がコマンドの終端として定義されているので、式の区切りが明確になるのだが、コマンドラインの場合 ; はコマンドの終端 or 複数コマンドのつなぎになってしまうので、これを区別することができないのであった*1。 if ~~ endの場合だけ、 ; をコマンドの終端とみなすように定義しようとしたのだが、これもやはりうまく行かなかったので諦め。

; を終端とすることはできないが、改行コードなら終端とみなすことができるので、複数行ではthenを省略可能にすることができると思う。 ・・・まぁ、まだ複数行サポートはしていないんですがね。

ifも追加したのでついでにunlessも追加した。 ifの条件判定を反転させるだけなのでかなり簡単に実装できた。 こちらは、bashにはないのでRuby構文のみ(unless ~ end)を実装した。

その他バグ修正

上記2つ以外は細かい修正になる。

  • 起動直後に $? を参照しようとしても何もコマンドを実行していないので、$? はnilになるので参照できずエラーになったのを直した
  • if/unless でelse がなくconditionがfalseの場合、$?を返さずにnilを返していたためにコマンド連結文(&&とか)がエラーになる問題を修正した
  • lexerがENVからIFSをみていたのだが、newしたときに読み込むようにした
    • 次回やるであろうENVの取扱いの修正を見越した修正

次回の実装予定

次はwhile/until文を追加したいと思っている。

が、しかし、その前にENVの取扱を見直したいのと、Reddish::Executorが太ってきたのでこれをどうにかしたいのと、そろそろ複数行サポートをしたいと思っている。 while/until文も実装自体はそこまで大変ではなさそうだと思うので、それ以外のところの修正が大変かもしれない。

*1:parserに構文を定義することはできるが、競合を起こしてしまってうまく動かない