[yuniFFI][nmosh] ボトムアップで作るC source generator

時間も無いので、先日の問題( http://d.hatena.ne.jp/mjt/20141011/p1 )はとりあえず忘れて、yuniFFIに必要な必要最低限のC source generatorを新たに作ってお茶を濁すことにした。JITCのようなパフォーマンスを気にするユースケースでなければ特に問題は無い。はず。
GaucheのciseのようにS式フォーマットのC言語を用意しないのは、GLSLやプロプライエタリGPUシェーダ言語を同じライブラリで扱いたいため。これらの言語はC言語"風"なので、C言語風構文を便利に出力できるようにしておくと便利なのではないか仮説。

コンテキストとインデント出力

なんと低レベルな。。ただ、機械生成されたソースをデバッグする機会は非常に多く、ここで手を抜いてしまうと非常に後悔するのは経験上明かなので。。

;; コンテキスト生成
(port->cemit port) ;; => <cemit-ctx>

;; インデント付き文字列出力
(cemit-open-line ctx) ;; ただのnewline
(cemit-open-line ctx str) ;; 改行して出力、糖衣構文
;; インデント無し文字列出力(自動改行付き)
(cemit-open-line/noindent ctx)
(cemit-open-line/noindent ctx str)
;; 文字列出力、改行なし、先行するopen-lineが無い場合はエラー
(cemit-put-string ctx str)

;; インデント追加
(cemit->> ctx)
;; インデント削除、インデントの無い状態の場合はエラー
(cemit-<< ctx)

以下の細かい出力手続きは、最終的にはこのインデント出力手続きを呼び出して実際の出力を行う。インデント無し出力はCプリプロセサマクロに使用する。
エラー検出をここで行う。手で使うようなケースでは邪魔なのでoffにできると良いかもしれないが、一般に、ここで検出できるエラーのある出力は何か間違ったことをしているので実際にコンパイラに掛ける前に落としておいた方が効率が良い。
yuniライブラリというか多分一般のschemeライブラリのルールとして、portに作用する手続きは副作用が有っても ! を付けない。

Cシンボル出力

基本的にSchemeシンボルを直接取る手続きは用意しない。nmoshにはsymbol GCが無いので、シンボルのinternを要求するAPIをyuniでは避けることにしている。
Cシンボルを生成するためのcgensymを独立したライブラリとして用意する。これらはput-stringで出力することを期待した文字列を生成する。

(make-cgensym) ;; => ctx
(cgensym ctx) ;; => string
(cgensym ctx OBJ) ;; => string

;; cemitコンテキストはgensymコンテキストを保持できる
(cemit-gensym cemitctx) ;; => string
(cemit-gensym cemitctx OBJ) ;; => string

gensymにコンテキストが有り、cemitに持たせるのは、デバッグ情報の抽出をサポートするため。

C定数出力

Schemeの文字列フォーマットとCの文字列フォーマットは一部一致しないので変換が必要。単純にwriteを使うことはできない。同様に、C整数もSchemeのフォーマットと一致しない。

;; C言語用のエスケープを適用した文字列へ変換
(string->ctoken str)
;; 数値のC言語表現文字列へ変換
(number->ctoken num)
(number->ctoken num base) ;; base = 10 | 16

"C言語風の定数表現文字列を得るライブラリ"はctokenとして独立させる。...ctokenという名称は微妙かもしれない。catom ..?
wide文字列は今のところサポートしない(ユースケースが無い)。
number→ctokenは自動でbaseを選択すると良いかもしれない。つまり、出力に必要なビット巾を計算した上で、popcountが一定の閾値以下であれば16進とかビットORで出力する等。

C式出力

C式出力はS式を取る。こればっかりは細かい手続きに分解してもあんまし意味が無さそうなので。。
今のところプリプロセサ出力はサポートしない。ヘッダ部分は泥臭く出力するしかない。
以下の定義でセミコロンの自動挿入が完全かどうかに自信が無い。。

;; ブロックを構成するC構文 
(begin <FORM> ...)
;; 行を構成するC構文
(assign <LHS> <RHS> ...) ;; 代入
;; 整数インスタンス。整数はそのまま書けば10進で出力される
(long <NUMBER>) ;; L
(longlong <NUMBER>) ;; LL
(unsigned long <NUMBER>) ;; UL
(unsigned longlong <NUMBER>) ;; ULL
(hex <NUMBER>) ;; 16進出力
;; 文字/文字列インスタンス
(string <STR>)
(char <STR>)
;; 構造体インスタンス
(aggregate <FORM> ...) ;; { FORM , FORM , ...} の形式に出力
;; 式を構成するC構文
(cond <TEST> <TRUE> <FALSE>) ;; いわゆる3項演算子
(call <NAME> <ARG> ...) ;; 関数/マクロ呼び出し
(cast <FORM>* <FORM>) ;; 型キャスト
(group <FORM>) ;; カッコ
;;; 演算子
(sizeof <FORM>)
(+ <FORM> ...)
(- <FORM> ...)
(* <FORM> ...)
(/ <FORM> ...)
(% <FORM> ...)
(bitwise-not <FORM>)
(bitwise-and <FORM> ...)
(bitwise-ior <FORM> ...)
(bitwise-xor <FORM> ...)
(not <FORM>)
(and <FORM> ...)
(or <FORM> ...)
(seq <FORM> ...) ;; カンマ
(dot <FORM> <FORM> ...) ;; . ドット参照
(ref <FORM> <FORM> ...) ;; -> アロー参照
(aref <FORM> <FORM> ...) ;; [] 配列参照
;; 宣言を構成するC構文
(var <NAME> <STRINGS>*) ;; 変数宣言
(var <NAME> <STRINGS>* <RHS> ...) ;; 変数宣言、初期値付
(function <NAME> <PREFIX-STRINGS>* ((<NAME> <STRINGS> ...) ...) <BODY> ...) ;; 関数宣言。プロトタイプ宣言は専用構文なし。
;; その他のC構文
(if <TRUE>)
(if <TRUE> <FALSE>)
(for <INIT> <CHECK> <UPDATE> <FORM>)
(while <CHECK> <FORM>)
(do-while <CHECK> <FORM>)
(switch <FORM> (case <CONST> <FORM> ...) ... (default <FORM> ...))
;; 制御構造
(continue)
(break)
(return)
(return <FORM>)

構文要素に手続きを渡した場合はcallbackされる。ただしhead部分をcallbackにはできない(エラーチェックできないため)。ciseのようなマクロなどの定義追加は提供しない。
空文はnilを使用する。偽はboolの出力に使用する可能性が有るため避ける。