ソースファイルの読み込み

お久しぶりです。

ASISのチュートリアルは大抵、コンテキストを用意したらすぐに翻訳単位の列挙をはじめてまして、大事なことがぽっかり抜けています。
それは……どうやってソースコードをASIS環境に読み込ませるか。どのソースファイルを対象にするか教えてないのに列挙開始したって何も出てきません、ええ。
で、チュートリアルでこの大事な部分がすっ飛ばされてるのには理由がありまして、この部分、実装依存なんです。

続きを読む

歴史語り

どう考えてもASISを完全マスターする時間はないので、発表の構成を考えます。
まず発表全体を見渡すと、他は新技術が目白押しです。メタプログラミングテーマで必須に思えるC++、Dすら無い。それどころかこれから作られる言語まで入ってます。
すると必然的にASISは歴史遺産担当枠ということに。

そもそもASISに深入りするぐらいならどう考えてもCIL覚えたほうが有意義ですのでASIS自体の使用例はシンプルなものでいいのでは?個人的にはIDEの入力補完に使えるような、特定スコープから見える識別子のリストアップみたいなのをやりたいですが、まあそれは置いておいて。最悪、.adsで宣言された関数名をリストアップしてテストコードのテンプレートを吐かせる、みたいな「それgrepでできるよ」でも……。(弱気)

とにかく冒頭5分は歴史に使いますか。

  • 1958年 LISPのマクロ
  • 1972年 C言語のマクロ
  • 1988年 generic.h (Google Code Searchで出てきたもの)
  • 1994年 C++のtemplateを使った素数計算 (買ってて良かった魔導書)
  • 1995年 SWIG
  • 1997年 REBOL
  • 1999年 Adga (twitterでは実に紛らわしいと評判です)
  • 2001年 CIL
  • 2002年 Template HaskellD言語……

SWIGって歴史ありますね……!!

で、ASISはというと、仮タイトルにしてますようにISOになったのが1999年。しかしそれ以前に、どうやらASIS83というのが存在していたようです。

http://www.sigada.org/WG/asiswg/ASIS_Background.html
http://www.sigada.org/WG/asiswg/ASIS-83.html

リンク切れでASIS83の資料が落とせねえええ!!
名前はどー考えてもAda83対応verだからASIS83で、絶対83年より後のはず。WGが1993からだそうですので、とりあえず1993年に挿入。お、FAQにも書いてある。

  • 1958年 LISPのマクロ
  • 1972年 C言語のマクロ
  • 1988年 generic.h
  • 1989年 ASIS was initiated as a STARS activity in 1989FAQに書いてある
  • 1993年 ASIS83
  • 1994年 C++のtemplateを使った素数計算
  • 1995年 SWIG
  • 1997年 REBOL
  • 1999年 Adga、ASIS 95 (ISO/IEC 15291:1999)
  • 2001年 CIL
  • 2002年 Template HaskellD言語……

おお、メタプログラミングの歴史に深く関わる立ち位置っぽいぞ!

ASISのビルド

メタプログラミングの会でASISネタで発表させていただけることになりました。
ただ……問題点がひとつ。

今までに私はASISを使ったことがありません。

というわけでしばらくASISの勉強シリーズにおつきあいください。ASISなら俺のほうが詳しいから発表代わりやがれって方は大歓迎です。

ASISってのは、ざっくり言ってしまうとCILのAda版で、Adaソースをパース/意味解析/変換/再出力するためのライブラリです。
マクロやテンプレート的な意味でのメタプログラミングライブラリではありませんので注意ください。
難点として、CILは賢明にも(極端に文字列処理に弱い)Cではなくてコンパイラのような処理が書きやすいことに定評があるO'Camlですので、とてもコードが書きやすいのですが、ASISはそれ自身(Cの次ぐらいに文字列処理に弱くさらに記述が長くて面倒なことに定評がある)Adaのライブラリですので、まあなんというか面倒だなあ、と、まだ全く手を付けていないのに確信してます。どうか外れてますように。

……で、まあ、ビルド方法。
AdaCore配布のGNATに付いてくるgnatstubやgnatpp(←ASISアプリケーション)をgccでも使いたくて、これらを野良ビルドしたことはありますのでその手順を書いておきます。こんなの発表に含めたってその場で試していただけるわけでもなし、ここにしか書きませんよ。ASISの日本語情報を読めるのはここだけ!

1. gccに対応したASISのダウンロード

先の説明をもっと正確に言いますと、ASISというのはライブラリそのものではなくてライブラリの仕様です。ですのでそれぞれのAdaコンパイラがASISも用意することになります。
ここではgcc(個人が趣味で使えるAdaコンパイラとしてはほぼ一択)に対応したASISをビルドします。

まず、GNAT GPLのサイトから2009を選択してasis-gpl-2009-src.tgzをダウンロードしてきます。メールアドレス聞かれますが空でOK。ここで最新の2010を使うと確か途中でコンパイルエラーになったような。gcc-4.5系列ですと2009でうまくいきます。

2. gccを途中までビルド

それから、当然皆さんの手元のマシンにはAdaも含めたgccがインストール済みとは思いますが、改めてgccを「途中まで」ビルドしてください。必要なのはビルド過程で生成される次の3つのファイルのみで、これらができたら止めちゃっていいです。

  • $(BUILDDIR)/gcc/version.o
  • $(BUILDDIR)/gcc/ada/snames.ads
  • $(BUILDDIR)/gcc/ada/snames.adb

3. 必要なファイルをコピー

asis-gpl-2009-src.tgzを展開して、その中のgnatディレクトリに上記snames.ads/snames.adbをコピーします。
あと細かいバージョンを揃えるために、gnatディレクトリにある他のファイルも、同名のファイルのgcc版で上書きしちゃいましょう。

4. ASISのビルド

make all gnatcheck gnatelim gnatmetric gnatpp gnatstub gnatsync

途中、バージョン関係のシンボルでリンクエラーになりますので、version.oもリンクするようにします。
libasis.a(そこまで進んでるならできてるはず)に放りこむのが一番手っ取り早いです。

ar -q lib/libasis.a version.o

5. ASISのインストール

添付makefileでインストールされる時のディレクトリ構成がちょっと気に入りませんので、一旦カレントディレクトリにでもインストールして再配置。

make INSTALL_DIR=$PWD install install-asistant install-tools
  • binの中のツール群を好きなところに移動。
  • include/asisの中のソースコードを好きなところに移動。gnatmakeの-aIまたは環境変数ADA_INCLUDE_PATHで参照します。
  • lib/asisの中のファイル群も好きなところに移動。gnatmakeの-aOまたは環境変数ADA_LIBRARY_PATHで参照します。
  • share/docの中のHTMLドキュメントも取っておきます。
  • .gprファイルを使うならasis.gprも取っておけばいいでしょう。中身はここで作ったディレクトリ構成に合わせて書き変える必要があります。

Cの.hからのトランスレータ

ある程度形になってきたためこっちに書きます。
http://panathenaia.halfmoon.jp/alang/headmaster/
Cの.hをAdaの宣言に直すトランスレータです。この間の愚痴はこれを作っていたのでした。

やっていることは、-fdump-ada-specの二番煎じ、SWIGの三番煎じ、CILの四番煎じです。とはいえ-fdump-ada-specはまだまだ実用には遠く、SWIGはAdaには対応していません*1ので、需要は一応あるはずです。

機能としては、少しだけ真面目にCの意味解析をしており、簡単なインライン関数であればそのままAdaのインライン関数にしたりします。インクリメントや条件演算子は関数内関数にしたり、引数が変更されている場合は(Adaの引数は変更できないので)一回ローカル変数に代入したりと、色々やってます。ある程度複雑なマクロも読み解いて、型として解釈可能ならsubtype、関数の別名ならrenames、式として解釈可能ならインライン関数にしたりします。ただ、現時点では関数型マクロはあきらめてます。関数型マクロをインライン関数に直すためには引数の型推論が必要ですから……その割には絶対無いと困る関数型マクロって意外と無くて……気合さえあればできそうな気はしているのですけどね……それよりもまだ基本的な部分に未実装なものが多すぎるため(演算子すら全部揃ってない)、とりあえず関数型マクロは抜きで、標準のヘッダーやwindows.hを扱えるようにしたいなあというのが当面の目標です。

Cのパーサは真面目に実装するとtypedef他色々特別扱いが必要なためパーサジェネレータは使えません。マクロもトランスレート対象にするためプリプロセス時の情報を残さないといけなかったため既存のプリプロセッサも使えません。CILすら使えません。全部手書きです。しくしく。
プリプロセッサについては、http://twitter.com/rui314/status/18821460437のようなドキュメントもあったのですが、それは無視して(というよりrui314さんに教えてもらう前に実装が終わってた)MCPPのpost-standardモード同等の処理をしています。要するに再帰による単純置換(手抜き)です。一部ヘッダーにそれではカバーし切れない(後続トークンの巻き込みが必要になる)部分がありましたので、展開後が引数付きマクロ名で次が"("で……みたいに判定して汚い処理をしてます。

……色々書こうとしていたのですが、実例見ていただいたほうがわかりやすいかな。現時点でもlibyamlのyaml.hぐらいは辛うじて実用になるかもしれないレベルで扱えており、The Village of Vampireのデータ保存部分をlibyamlに移行できて悦に入ってます。(これが需要の100%)

本当は一年半前のAda Hackathonのネタにしようと思ってたプロジェクトなんですよ……ここまで長かった……。まさにWhen I (yt) started to write headmaster I thought it was going to take two weeks. Exactly a year has passed since then and I am still fixing bugs in it.ですよ……。

さくっとAda以外にも対応して放棄してしまったwindows.h for DのリベンジやJEDIの独自色が強すぎるwin32ヘッダーを滅ぼしたりしようと野望を抱いていたのですが、私一人の労力では無理ということがわかってきました。コードはgithubに置いておきました(O'Caml 3.12.0以降が必要です)ので、物好きな方は弄ってやってください……。(でも実用にしようと思えばSWIG弄ったほうが早いだろうという二番煎じプロジェクトの弱点がっ)
個人的にはbzr派になりたいのです、が、いつまでたってもnested treeが使えるようにならないのとlaunchpad重すぎなので、とうとうgithubのアカウントを取ってしまいました……。

String Encodingぅ

Ada2012ではUTFの変換ライブラリが用意されます。

http://www.ada-auth.org/standards/12rm/html/RM-A-4-11.html

StringからUTF_8_Stringに変換(同じ型ですが)するEncode関数が用意されていることで、文字コードの違いが明確になっています。Stringには現在のロケール、Wide_Stringには現在のロケールの文字をpacked形式で、UTF_8_StringにはUTF-8、UTF_16_Wide_StringにはUTF-16、といった使い分けがなされることになるのでしょう。それとは別にUTF_Stringとして、Stringにバイナリ列を詰め込んだものを処理できるようにもなっています。

GNAT GPLの実装を見たらなんかStringにはUTF-32の0からFFまでが入るような感じで激しく不安に駆られるのですが、文字コードについてAdaCoreに期待してはいけないというのは私の中で了解事項ですので気にしないことにします。

しかし、それを差し引いても、よく見てみるとこのライブラリはいろいろ足りないような……。

まずUTF-32のBOMがありません、というかUnicodeを扱う上で一番扱いやすい形式のはずのUTF-32がすっぽり抜け落ちています。このライブラリを実装するにも、UTF-8と16の間の変換で一時的にでもUTF-32にするはずなのに。まあ、変換は一文字単位で行われるはずでしょうけれど……。

……そう、一番大きな点として、一文字単位で処理をするための関数がこのライブラリには一切ありません。実装上必要なはずなのにユーザーに解放されていません。全データをひとつのStringに入れて、全部スタック*1の上で処理しろとおっしゃいますか。
例えばストリームから読み出したデータを順次変換するような処理を考えた場合、最低でもmblen相当は必要になると思うんですよ。まあUTF-8/16の一文字分を切りだしてくる処理なんてすげー簡単ですが、ライブラリにあるに越したことはないと思うんですよ。それ以外にも、文字列の一部だけを処理するための関数があると便利と思うんですよ。Ada.Text_IO.Get_Lineみたいなプロトタイプで。

-- 一文字だけ変換
procedure Encode (
   Source : in String;
   Next : out Positive; -- Source中の次の文字のインデックス
   Result : out String;
   Last : out Natural); -- 変換後の文字がResultに格納された終端

これですとスタックも使いませんしね。
こういった明らかに必要な関数が足りてないのに、滅多に使わないであろうBOM周りだけ(UTF-32抜きで)執拗にサポートされてるものですから、溜息が……。
こうして、Adaにまたひとつ使えない標準ライブラリが増えたのでした。いや、単純な用途には使えますけどね……。

……いやいや、Ada2012まであと2年ある!きっと、きっと……!!

追記

http://www.adaic.com/standards/05rm/html/RM-3-5-2.html

CharacterがBMPの最初の256文字(Latin-1相当)というのは決まってるのか……orz
しかしまあStringにLatin-1しか入れられないとすると多くのパッケージでファイル名にLatin-1しか使えないことになってしまいますし、事実GNATのライブラリがIO周りでは素通し(or統一感のない迷惑な変換)で全然Latin-1として動作してないというのもありますし、a-chlat9.adsを見るに、本当にCharacterをBMPの最初の256文字にしようとしているなら「Summary of Changes from Latin-1 => Latin-9」なんて変更があるわけ無いし(Latin-9で164に割り当てられているEuro-SignUnicodeですとU+20ACですので)、混乱状態なのは確かです。
StringはUTF-8固定、Wide_StringはUTF-16固定、Wide_Wide_StringはUTF-32固定が一番嬉しいんですけどねえ……。

*1:AdaのStringはスタックに確保されます。GNATの場合関数の返値のみセカンダリスタック(実体はmallocされたメモリ)なので実は気にしなくていいのかもしれません。

Ada2012

いつの間にかAda2012の規格が公開されていました。
http://www.ada-auth.org/standards/ada12.html
あれ……with private/end private/limited new論争とか、foreachのようなものとか、GNATがフライング実装したif式/case式とか、VectorsのReference関数とか、面白そうなissueほど反映されてない気が……。
ま、まだ2年も先だし、これから追加されるに違いない、そう信じよう。汗が出るのは暑いせいですよ、きっと。

とりあえず、面白そうなのを紹介。

use all type http://www.ada-auth.org/standards/12aarm/html/AA-8-4.html

  • Ada86 …… useで丸ごとパッケージ全部の名前空間をマージ。→ 名前空間の汚染が酷過ぎる。useは禁止。→あれ、演算子オーバーロード使えないんじゃね?
  • Ada95 …… use typeで演算子だけ持ってこれる。これで演算子オーバーロードも使えるよ!でも他はパッケージ名からちゃんとだらだら長く書こうね!
  • Ada2005 …… 進展なし。
  • Ada2012 …… プリミティブは型に属してるんだから混同することもないよね。use all typeでプリミティブもパッケージ名無しで使えるように!

前にこれが正しいと主張していた論文を読んだ気がします。今導入されている形とは別のオブジェクト指向モデルを提案してるやつだったでしょうか、忘れた。

Quantified Expressions http://www.ada-auth.org/standards/12rm/html/RM-4-5-7.html

if (for all I in Array1'Range | Array1(I) = True) then
   ...
elsif (for Some I in Array1'Range | Array1(I) = True) then
   ...

条件式内でループ。allは、全部TrueならTrue。Someは、ひとつでもTrueならTrue。and/orですね。マルチコア対応コンパイラだったりすると、並列処理されたりするんでしょう。
で、この"Some"というのは確定なのか!?予約語じゃないのにこんなところで固定ワードとして使っていいのか?マジですか?

Indefinite_Holders http://www.ada-auth.org/standards/12rm/html/RM-A-18-18.html

私これ自分のコードで必要になって作りました、何度も何度も……。そうか……ようやく……。
……これこそ言語機能として組み込んで欲しいところ……そうすれば真・動的配列も付いてきますよ……。

他には、組込み向け言語としては必須のBoundedコンテナや、String/Wide_String/Wide_Wide_StringをUTF-8/16/32と見立てて変換を行う関数群などが目につくところです。
全体的に、「まだ」(であって欲しい)、86→95→2005に比べて、大人しい改定ですね。これから派手になるんですよね!?

それ例外でできるよ!

http://kmonos.net/wlog/101.html#_1934090919 の記事に対して、「それ例外でできるよ」とつぶやいたところ、「再帰では最内周で補足されてしまうため使えない」と言い伏せられたため、無理やり解決してみました。
恐らく何言ってんだか何やってんだかわからないと思いますが、私もわかってないです。

続きを読む