煽られるように開発中の言語(Glue)について説明・・・

Mon Dec 15 18:50:28 JST 2014 (modified: Sun Oct 1 10:50:27 JST 2017)
views: 2062, keywords:シェルスクリプト,GlueLang,研究,グルー言語を作る この記事は最終更新日が6年以上前のものです。

あわわわわ。

引用するのも何か申し訳ないのですが、ここ数日この件で2,3人の方から感想を求められたので、感想を・・・。

アホ丸出しです。でも、なるほどです。「そういう解釈になるのかー」と。

私もホソボソと・・・

言語を作っております。実は「36歳の誕生日にグルー言語作る宣言をせざるを得なくなった」ということがありまして、作っております。「ある人」が誰かは・・・推して知るべしです。ちゃんと書けばいいのですが、どうも北陸の人間特有の引っ込み思案があり、ブログというものでインタラクションするのが失礼なのではないかと気が引け・・・。その割に人のツイートを引用してますが・・・。

https://github.com/ryuichiueda/GlueLang/tree/master/PROTOTYPE

んで、現在の状況ですが、大学にこの件で紀要を書いて一旦ストップし、ロボットの方の研究をしています。これは決して消極的な理由と捉えていただきたくないのですが、こちらでもやりたいことだらけで・・・。年度内にはもう一度、まとまった日数を費やそうと考えています。

ただ、紀要を書いて満足してインターネット上に何も公表していなかったので、この際何をしているのかを書いておこうと思います。私個人がこのような状況なので、考え方だけでも何か貢献できないかと。

なぜ新言語か

コアが増えるとパイプライン処理が簡単に書けるようにしないといろいろ面倒で、ただ既存の言語だとパイプライン処理を持ち込むと異物感が甚だしいので、どうしても新しい言語を作るという発想になります。シェルも、インタラクティブ性が重視されすぎていて言語としては弱いので、新しいもの、という発想になります。ということで新言語を作り出しました。私がちゃんと作り切ることができるかどうかは分からないのですが、そういう発想には確信を持っています。

私の場合は言語の解釈以外のものを全て外部コマンドに任せてしまおうという考えでやっているので、たぶんMatzさんのStreemとは直交する、すなわち用途が一緒のようで違ったものになると思います。ということで、言語といっても私の場合はシェルを作ることになります。最近もdashのコードのパーサの部分を読んでいますが、なかなか手ごわく・・・。

とりあえず何を作ったか。

次のようなコードを入力すると・・・

import /bin/ as b
   import /usr/bin/ as ub
   
   proc main file:
       file f = cattac $file
       b.cat $f
   
   func cattac file:
       b.cat $file
       ub.tail -r

次のような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:
       file f = cattac $file
       b.cat $f
   
   func cattac file:
       b.cat $file
       ub.tail -r

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:
       str s = cattac
       echo $s
   
   func cattac file:
       b.cat $file
       ub.tail -r

シェルの場合、変数は文字列しかないのでこれで十分です。他の型を作るつもりはありません。コマンドは字を出し入れするから分かりやすいのであって、別のものがあったら変換しないといけなくなり、ややこしくなります。ただ、作るかも・・・(どっちや)。

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:
       str c = ulb.retu < $a
       b.test "$c" = "$b"
   
   proc main file num:
   | checkColnum $file $num:
       b.echo "OK"
   | othewise:
       b.false

.glueファイル同士のインクルード等

全部コマンドとしてimportで。基本、全部コマンドで実装しておけば簡単にくっつけられるし、bashからでもzshからでも使えます。ですから、あんまり困らんのじゃないかと思ってます。誰か困ったら慌てて仕組みを作ればいいんじゃないかと。

エラー処理

io.glueを変換したものをご覧いただければ分かりますが、コマンドがエラーを吐くと関数に飛んで止まります。

長いプログラムの例

どうぞ。

https://github.com/ryuichiueda/GlueLang/blob/master/PROTOTYPE/SAMPLE_KIYOU2014/index.glue

↓この本で書いたindex.cgiのGlue版です。

動作検証もしました。しかし、シンタックスハイライトもなければシェルスクリプトに慣れ切っているので、ここまで長くなると自分でもピンとこなかったり・・・。

最後に

当然ですが、これからも研究課題として取り扱っていきます。、手伝ってくれないかなあ・・・(ボソ)。あ、READMEとか、Streemのものを参考にさせていただいて書き直そうと思います・・・。

ノート   このエントリーをはてなブックマークに追加 
 

prev:第14回シェル芸勉強会で個人的に面白かった解答 next:自由に対する私的なステートメント

やり散らかし一覧

記事いろいろ