CコードへのHaskellインタフェースを書く: hsc2hs

hsc2hsコマンドを使うと、CコードのHaskellバインディングを書く作業を一部自動化することができる。このコマンドは、特別な構文要素の埋め込まれたHaskellのコードを読み、Cのヘッダから得た情報を使ってこれらの要素を処理し、本当のHaskellファイルを出力する。この特別な構文要素は、CのデータをHaskellからアクセスするにあたって必要なことを扱う。

hsc2hsはCファイルとCヘッダを出力することもある。Cファイルには、プログラムにリンクすべき追加のC関数が置かれ、ヘッダファイルは、そのHaskellモジュールがコンパイルされてできるCコードからincludeされるものである。この二つのファイルは#defという構文要素(下記参照)が使われたときに作られる。

hsc2hsは実際には直接Haskellコードを出力するわけではない。問題のヘッダをインクルードするCプログラムを作り、自動的にコンパイル・実行する。このプログラムがHaskellコードを生成する。

以下では、「Haskellファイル」は主たる出力ファイル(通常.hs)であり、「Haskellファイルのコンパイル結果」は、ghcがHaskellファイルをコンパイルしてできたもの(.hcファイル)であり、「Cプログラム」はHaskellファイルを出力するプログラムであり、「Cファイル」は場合によって出力されるCファイルであり、「Cヘッダ」はそのヘッダファイルである。

コマンド行構文

hsc2hsは、引数として、入力ファイルと、振る舞いを変更するフラグを受け付ける。

-o FILEまたは––output=FILE

Haskellファイルの名前。

-t FILEまたは––template=FILE

テンプレートファイル(下記参照)。

-c PROGまたは––cc=PROG

利用するCコンパイラ(デフォルト: gcc)。

-l PROGまたは––ld=PROG

利用するリンカ(デフォルト: gcc)。

-C FLAGまたは––cflag=FLAG

Cコンパイラに渡す追加のフラグ。

-I DIR

Cコンパイラに渡される。

-L FLAGまたは––lflag=FLAG

リンカに渡す追加のフラグ。

-i FILEまたは––include=FILE

適切な#includeがソース中に置かれたのと同様。

-D NAME[=VALUE]または––define=NAME[=VALUE]

ソース中に適切な#defineが置かれたのと同様。

––no-compile

中間のCプログラムをディスクに書き込んだ後停止する。中間Cファイルの名前は、入力ファイル名の.hsc_hsc_make.cで置き換えたものである。

-k or ––keep-files

通常通りに進行するが、中間ファイルを全く削除しない。

-x or ––cross-compile

クロスコンパイルモードを起動する(11.2.4. クロスコンパイルを見よ)。

––cross-safe

.hscディレクティブを、--cross-compileモード(11.2.4. クロスコンパイルを見よ)が対応しているものに限定する。これは、安全にクロスコンパイルされねばならない.hscファイルがあって、クロスコンパイル不能な要素が忍び込むことがないようにしたい場合に便利なはずである。

-?または––help

利用可能なフラグの要約を表示し、成功裡に終了する。

-Vまたは––version

バージョン情報を表示し、成功裡に終了する。

入力ファイルは.hscで終わっていなければならない。(これは素のHaskellソースでなければならない。文芸Haskellは未対応である)。出力ファイルはデフォルトでは.hsc接尾辞を次のもので置き換えた名前になる。

.hs Haskellファイル
_hsc.h Cヘッダ
_hsc.c Cファイル

CプログラムはHaskellコンパイラでコンパイルされる。これにより、HsFFI.hが使える。これは、自動的にCプログラムにincludeされる。

入力の構文

特殊な処理は全て#演算子で起動される。#を文字どおり出力したい場合は、##と二回書けば良い。文字列リテラルおよびコメントの中の#は処理されない。

#の後にはスペースやタブ(省略可能)が続き、その後に、アルファベットと数字からなる、処理の種類を示すキーワードが書かれ、その後に引数が置かれる。引数は、Cの式をコンマで区切ったような形をしている(括弧では囲まない)。引数が終わるのは、対応するもののない)]}が現れるか、行の終わりに達したとき(ただし、() [] {} '' "" /**/のいずれにも囲まれておらず、バックスラッシュが前置されてもいない場合に限る)である。バックスラッシュと改行の対は消される。

さらに、#{何か}#何かと同様だが、終わりが明確なので、行の最後に置いたり括弧の中に置いたりする必要がない。

個々のキーワードの意味は次の通り。

#include <file.h>, #include "file.h"

指定されたファイルが、Cプログラム、Haskellプログラムのコンパイル結果、Cヘッダにそれぞれincludeされる。<HsFFI.h>は自動的にincludeされる。

#define 名前, #define 名前 値, #undef 名前

#includeに似ている。#include#defineは一つのファイルに二回置かれることがあることに注意。

#let 名前 引数列 = "定義"

Haskellソースに適用されるマクロを定義する。引数名はコンマで区切り、括弧では囲まない。このようなマクロは、#名前で始まる構文要素で起動される。この定義は、Cプログラムにおいて、括弧で囲んでprintfの引数として使われる。引数を参照するには、引用符を閉じ、引数を置き、再び引用符を開くことでCの文字列リテラルの連結を利用する。あるいはprintfの書式指定子を使っても良い。引数は、Cプリプロセッサの#引数記法を使って明示的に文字列化するのでない限り、文字列として渡されなければならない。

#def Cの定義

この定義(関数定義、変数定義、構造体定義、typedef)がCファイルに書かれ、そのプロトタイプかextern宣言がCヘッダに書かれる。インライン関数も正しく扱われる。構造体定義とtypedefはCプログラムにも書き込まれる。inlinestructtypedefの各キーワードはdefの直後に来なければならない。

#if 条件 , #ifdef 名前, #ifndef 名前, #elif 条件, #else, #endif, #error メッセージ, #warning メッセージ

条件コンパイルディレクティブは、Cプログラム、Cファイル、Cヘッダに変更なしで渡される。Cプログラムにも置かれるので、Haskellファイルの適当な部分が飛ばされることになる。

#const Cの式

式はlongまたはunsigned longに変換可能でなければならない。その値(リテラル、またはリテラルを符号反転したもの)が出力される。

#const_str Cの式

式はcharへの定数ポインタに変換可能でなければならない。その値(文字列リテラル)が出力される。

#type Cの型

Cの数値型に対応する、Haskellでの同等の型が出力される。これは、{Int,Word}{8,16,32,64}FloatDoubleLDoubleのいずれかである。

#peek 構造体型, フィールド

Cの構造体のフィールドを読み出す関数が出力される。型はStorable b => Ptr a -> IO bである。#peek#pokeを使って、与えられたCの構造体についてのStorableクラスの演算を定義できるようにする、という意図である。(ライブラリ説明書のForeign.Storable参照)

#poke 構造体型, フィールド

pokeについても同様。型はStorable b => Ptr a -> b -> IO ()になる。

#ptr 構造体型, フィールド

構造体のフィールドへのポインタを作る。型はPtr a -> Ptr bになる。

#offset 構造体型, フィールド

構造体型中でのフィールドのオフセットをバイト単位で計算する。型はIntになる。

#size 構造体型

構造体型の大きさを、バイト単位で計算する。型はIntになる。

#enum 型, 構築子, 値, 値, ...

#constを使った複数の定義の代替となる略記法。それぞれのはCの整定数(例えば列挙値)の名前である。この名前は、アンダースコアの次の文字を大文字にし、それ以外を小文字にし、アンダースコアを取り去ることで、Haskellの名前として使われる。と書く代わりにHaskellでの名前 = Cの値と書くことで、自分で変換を指定することができる。この場合、Cの値は任意の式であって良い。Haskellでの名前は、指定されたを持つものとして定義される。定義は、指定された構築子(実際には式でも良いし、空でも良い)を、適当な整数値に適用したものである。一つのに対して複数の#enum定義があっても良い。この構文要素は型定義自体を出力するわけではないからである。

自分で構文要素を用意する

#const#type#peek#poke#ptrhsc2hsと結びつけられておらず、CプログラムにincludeされるCテンプレートであるtemplate-hsc.hで定義されている。自分で構文要素やテンプレートを用意し、それを使うこともできる。#要素で、キーワードが未知であるものは、全てCテンプレートが対応するものとみなされる。

Cテンプレートでは、名前にhsc_を前置したマクロまたは関数を定義する。この関数は、構文要素を処理し、展開結果を標準出力に書き出す。例としてtemplate-hsc.hを参照せよ。

このようなマクロはソース中で直接定義することもできる。これは、#letマクロを使ったものに展開される#let風マクロを作るのに便利である。通常の#letでは、マクロ名にhsc_を前置し、定義をprintfの呼び出しで包む。

クロスコンパイル

hsc2hsは通常、Cプログラムを作成し、コンパイルし、実行することで動作する。しかし、この方策はクロスコンパイルでは機能しない。この場合、Cコンパイラはホスト機械ではなくターゲット機械で走るコードを生成するからである。この状況のために、hsc2hs --cross-compileという特殊なモードがある。これは、コンパイルのみ(細かく言うと、コンパイルの成否)によって情報を抽出して.hsを生成することができる。

--cross-compileは、.hsc構文の一部にしか対応していない。以下のものは未対応である。

  • #{const_str}
  • #{let}
  • #{def}
  • 自分で定義した構文要素