煽られるように開発中の言語(Glue)について説明・・・
Mon Dec 15 18:50:28 JST 2014 (modified: Sun Oct 1 10:50:27 JST 2017)
views: 2291, keywords:シェルスクリプト,GlueLang,研究,グルー言語を作る この記事は最終更新日が7年以上前のものです。
あわわわわ。
@okapies 外部コマンドを起動しなくてもいい、マルチコア対応のシェルスクリプトを想定しています。ログ解析とかテキスト加工とか。
— Yukihiro Matsumoto (@yukihiro_matz) 2014, 12月 11
引用するのも何か申し訳ないのですが、ここ数日この件で2,3人の方から感想を求められたので、感想を・・・。
なるほど
— Ryuichi UEDA (@ryuichiueda) 2014, 12月 15
アホ丸出しです。でも、なるほどです。「そういう解釈になるのかー」と。
私もホソボソと・・・
言語を作っております。実は「36歳の誕生日にグルー言語作る宣言をせざるを得なくなった」ということがありまして、作っております。「ある人」が誰かは・・・推して知るべしです。ちゃんと書けばいいのですが、どうも北陸の人間特有の引っ込み思案があり、ブログというものでインタラクションするのが失礼なのではないかと気が引け・・・。その割に人のツイートを引用してますが・・・。
https://github.com/ryuichiueda/GlueLang/tree/master/PROTOTYPE
んで、現在の状況ですが、大学にこの件で紀要を書いて一旦ストップし、ロボットの方の研究をしています。これは決して消極的な理由と捉えていただきたくないのですが、こちらでもやりたいことだらけで・・・。年度内にはもう一度、まとまった日数を費やそうと考えています。
ただ、紀要を書いて満足してインターネット上に何も公表していなかったので、この際何をしているのかを書いておこうと思います。私個人がこのような状況なので、考え方だけでも何か貢献できないかと。
なぜ新言語か
コアが増えるとパイプライン処理が簡単に書けるようにしないといろいろ面倒で、ただ既存の言語だとパイプライン処理を持ち込むと異物感が甚だしいので、どうしても新しい言語を作るという発想になります。シェルも、インタラクティブ性が重視されすぎていて言語としては弱いので、新しいもの、という発想になります。ということで新言語を作り出しました。私がちゃんと作り切ることができるかどうかは分からないのですが、そういう発想には確信を持っています。
私の場合は言語の解釈以外のものを全て外部コマンドに任せてしまおうという考えでやっているので、たぶんMatzさんのStreemとは直交する、すなわち用途が一緒のようで違ったものになると思います。ということで、言語といっても私の場合はシェルを作ることになります。最近もdashのコードのパーサの部分を読んでいますが、なかなか手ごわく・・・。
とりあえず何を作ったか。
次のようなコードを入力すると・・・
import /bin/ as b
import /usr/bin/ as ub
:
proc main file= cattac $file
file f .cat $f
b
:
func cattac file.cat $file
b.tail -r ub
次のようなbashのコードに変換して実行するトランスレータを作ったところです。
ERROR_EXIT(){
rm -f /tmp/$$-*
exit 1
}
ERROR_CHECK(){
"$(tr -d ' 0' <<< ${PIPESTATUS[@]})" = "" ] && return
[ ERROR_EXIT
}
trap ERROR_EXIT 1 2 3 15
foreach(){
while read line ; do
"$1" $line
ERROR_CHECK
done
}
cattac(){
/bin/cat $1 | /usr/bin/tail -r
ERROR_CHECK
}
main(){
f=$(mktemp /tmp/$$-f)
ERROR_CHECK
cattac $1 > $f
ERROR_CHECK
/bin/cat $f
ERROR_CHECK
}
main "$1"
ERROR_CHECK
rm -f /tmp/$$-*
やっていることは簡単で、単に「cat | tail -r」をしているだけです。
ちょっと動かしてみます。「./glue SAMPLE_SCRIPTS/io.glue」が変換前のスクリプトで、内部でbashに変換されて実行されます。(久しぶりに動かすので、ドキドキしましたがちゃんと動きました。)
uedambp:PROTOTYPE ueda$ seq 5 | ./glue SAMPLE_SCRIPTS/io.glue
5
4
3
2
1
仕様等
もう一度、新言語のスクリプトを示します。だいたいこのコードにやりたいことが凝縮されています。
import /bin/ as b
import /usr/bin/ as ub
:
proc main file= cattac $file
file f .cat $f
b
:
func cattac file.cat $file
b.tail -r ub
PATHに代わるimport
まず、importですが、これはPATHに代わる仕組みです。この例では、/bin/下のコマンドに「b.」、/usr/bin/下のコマンドに「ub.」とつけています。これは移植性の改善を狙ってのことで、将来的には
import /usr/local/bin/posix/ as posix
というように、「移植性が本当に必要ならばPOSIX準拠のコマンドを置いてそれしか使わないようにすればいいんじゃないの?」ということができるようにしたいと。そんなコマンドあるんかということですが、作るしかありません。そしてそういうコマンドのパッケージが、この言語のライブラリに相当するものになるわけです。
移植するときは、コマンド(あるいはコマンドのソース)ごとコピーです。移植性はコマンドに任せます。いろんな人がシェルスクリプトの移植性に対してああだこうだ議論してますが、基本的に自分の考えはこのようなものです。
んで、「移植性なんか関係ない。書き散らかしたい」という普段の私みたいな奴のために、PATHが通ってるコマンドでは「b.」なんてプレフィックスはつけなくていいようにしています。VBか何かでOption Strictというのがありましたが、そういうオプションでコントロールしてもいいかもしれません。
中間ファイル
基本的に 「file hoge = コマンド」(io.glueの5行目)と書いておけば勝手にファイルができて、処理が終わったら勝手に消えるようになってます(変換後のbashスクリプトを参照のこと)。実際の中間ファイルにはランダムに名前がつけられますが、それを「hoge」で参照できるようになっています。
- 中間ファイルの後始末が面倒
- 置き場所やパーミッションを考えるのが面倒
という中間ファイルに関するシェルスクリプトの面倒さをこれで一網打尽にしたいと。
変数
別の例で、str.glueというコードを示します。基本的にはfileの代わりにstrと書けば、その変数にコマンドの出力が格納されます。日頃言っているように変数あんまり使ったらいけませんが。
import /bin/ as b
import /usr/bin/ as ub
:
proc main= cattac
str s $s
echo
:
func cattac file.cat $file
b.tail -r ub
シェルの場合、変数は文字列しかないのでこれで十分です。他の型を作るつもりはありません。コマンドは字を出し入れするから分かりやすいのであって、別のものがあったら変換しないといけなくなり、ややこしくなります。ただ、作るかも・・・(どっちや)。
proc、funcとインデント
funcでは縦に並べたコマンドがパイプラインで接続されます。procだと普通のシェルスクリプトと同様に順にコマンドが実行されます。上のio.glueやstr.glueの例だと、b.catとub.tailがパイプで接続されます。そして、funcもまた、標準入出力を入出力とします。
この仕様はとても悩んだのですが、基本的に箇条書きをすればプログラムが書けるようにしたかったので、同じように書いてもprocとfuncで違う動きをするという選択をしています。ただ、ちょっとなーと悩んでいるところでもあります。
あと、procやfuncの一塊には「ブロック」と名前をつけていますが、他にawkのコードを書くようなブロック、ヒアドキュメントを書くようなブロックがあったら面白いなと。別にperlやrubyやpythonのブロックがあっても構わないと思います。
2段以上のインデント
書けないようにしたいです。ロジックは必ず1段の箇条書きでまとめてしまえないようでは(以下略。過激だ・・・)
条件分岐
2段インデント禁止と微妙に矛盾しますが、今のところifは次のように書きます。testというブロックを作ると、こいつが終了ステータスを返してくるので、それをprocでHaskell風に使っています。ただ、if文にもいろんなパターンがあるので、これで済まないんじゃないかなーとか悩んでますが。
import /bin/ as b
import /usr/bin/ as ub
import /usr/local/bin/ as ulb
:
test checkColnum a b= ulb.retu < $a
str c .test "$c" = "$b"
b
:
proc main file num| checkColnum $file $num:
.echo "OK"
b| othewise:
.false b
.glueファイル同士のインクルード等
全部コマンドとしてimportで。基本、全部コマンドで実装しておけば簡単にくっつけられるし、bashからでもzshからでも使えます。ですから、あんまり困らんのじゃないかと思ってます。誰か困ったら慌てて仕組みを作ればいいんじゃないかと。
エラー処理
io.glueを変換したものをご覧いただければ分かりますが、コマンドがエラーを吐くと関数に飛んで止まります。
長いプログラムの例
どうぞ。
https://github.com/ryuichiueda/GlueLang/blob/master/PROTOTYPE/SAMPLE_KIYOU2014/index.glue
↓この本で書いたindex.cgiのGlue版です。
動作検証もしました。しかし、シンタックスハイライトもなければシェルスクリプトに慣れ切っているので、ここまで長くなると自分でもピンとこなかったり・・・。
最後に
当然ですが、これからも研究課題として取り扱っていきます。腕利きの@bsdhackさんあたり、手伝ってくれないかなあ・・・(ボソ)。あ、READMEとか、Streemのものを参考にさせていただいて書き直そうと思います・・・。