スキーマファイルの文法

はじめに

SERは、Cプログラマをターゲットにしたライブラリですが、 スキーマコンパイラの文法は、かなりCからかけ離れたものとなっています。

わざわざこのような文法にしたのは、以下の理由によります。

  1. Cの宣言の構文は腐っているので、それに似せるのにはかなり抵抗があった。
  2. Cの宣言の構文は腐っているので、それに似せようとすると パーサを書くのが面倒になる。
  3. Cの宣言の構文は腐っているので、私の知る限り、 Cを日常的に使っているプログラマでもちゃんとした読み方を知らない人は多い。 そして、宣言を正しく読めないと、SERの使用に支障を来たす (例えば、SER_malloc()の際に渡すデータ型指定が書けない)。

ここでは、BNFを使わずに、例と自然言語で文法を解説します。 曖昧さのない定義を知りたい人は、 SER配布ディレクトリ以下のcompiler/schema.y を参照してください。


コメント

コメントは、C流/C++流の2種類が使えます。

/* C流のコメント */
// C++流のコメント

Cパート

行頭から始まる"begin{C}" と "end{C}"で囲まれた部分は、 「Cパート」と呼び、AP向けヘッダファイルの冒頭にそのまま出力されます。

「Cパート」の主な用途は、スキーマファイルで不完全型(後述)として 使用する型を定義しているヘッダファイルの#include文を記述することです。

begin{C}
#include <X11/X.h>
end{C}

「Cパート」は、スキーマファイルの任意の箇所に複数回記述可能ですが、 AP向けヘッダファイルに出力される時には、全て冒頭にまとめられます。

スキーマ名の宣言

スキーマファイルでは、まず最初にスキーマ名を宣言します。

schema hoge;

スキーマ名は、以下の箇所で使用されます。


型宣言

基本構文

型宣言の基本構文は、以下のようになります。

typedef 型名 is 型指定子の並び ;

型指定子にて、型名を修飾します。 型指定子の一覧は、以下の通りです。

配列・可変長配列・ポインタ・構造体・共用体については、 再帰的に(繰り返して)適用できます。

スキーマファイルでは、前方参照が可能です。 よって、相互にポインタを張り合う構造体を定義する際に、 わざわざ片方の構造体のタグだけを不完全型として宣言する必要はありません (SERにおける「不完全型」は、また別の意味を持つ)

基本型

基本型について別名を宣言する時には、以下のように記述します。

スキーマファイル
typedef Hoge is unsigned int;
対応するCのコード
typedef unsigned int Hoge;

配列

配列型を宣言する場合には、以下のように記述します。

スキーマファイル
typedef HogeArray is array[10] of Hoge;
対応するCのコード
typedef Hoge HogeArray[10];

C言語には多次元配列は存在しないので (規格書の脚注に現れるとかいう話は置いといて)、 スキーマファイルにも多次元配列のための記法はありません。

ただし、「配列の配列」なら、以下のようにして宣言することができます。:-p

スキーマファイル
typedef HogeArray is array[10] of array[5] of Hoge;
対応するCのコード
typedef Hoge HogeArray[10][5];

可変長配列

可変長配列を宣言する場合には、以下のように記述します。

スキーマファイル
typedef HogeVArray is varray of Hoge;
対応するCのコード
/* 要素数は、SERの実行時ライブラリが保持する */
typedef Hoge *HogeVArray;

ポインタ

ポインタは、以下のように記述します。

スキーマファイル
typedef HogeP is pointer to Hoge;
対応するCのコード
typedef Hoge *HogeP;

列挙

列挙は、以下のように記述します。

スキーマファイル
typedef HogeType is enum {
  HOGE_TYPE_A,
  HOGE_TYPE_B,
  HOGE_TYPE_C
};
対応するCのコード
typedef enum {
  HOGE_TYPE_A = 1,
  HOGE_TYPE_B,
  HOGE_TYPE_C
} HogeType;

Cとは異なり、各列挙子に明示的に値を指定することはできません。 また、列挙子の整数値を1から初まるようにしているのは、 何らかのバグ(領域破壊等)で列挙に妙な値が入る時、 経験的に0が入ることが多いように思われるので、 それが正常な値として扱われないようにするためです。

構造体

構造体は、以下のように記述します。

スキーマファイル
typedef HogeS is struct {
    a   : int;
    b   : array[10] of int;
    c   : pointer to Hoge;
};
対応するCのコード
typedef struct {
    int     a;
    int     b[10];
    Hoge    *c;
} HogeS;

「このメンバは、実行時には必要だが、一時的なデータなので、 シリアライズする必要はない」という場合、 メンバに対してtransientの指定をすることができます。 transient指定されたメンバは、たとえそれがポインタであったとしても、 ガベージコレクトの際に無視されるので注意してください

# この仕様は失敗か?

共用体

共用体は、Cの宣言とは大きく異なります。

スキーマファイル
typedef HogeU is union HogeType {
  case HOGE_TYPE_A:
    a : A_Hoge;
  case HOGE_TYPE_B:
    b : B_Hoge;
  case HOGE_TYPE_C:
    c : C_Hoge;
};
対応するCのコード
typedef struct {
    HogeType  tag;
    union {
        A_Hoge a;
        B_Hoge b;
        C_Hoge c;
    } u;
} HogeU;

共用体は、Cの文法のままでは、実行時に実際に格納されているのが どのメンバであるのか特定できないので、 列挙型のタグを指定する必要があります。

C言語では、enumと共用体の構造体として表現されます。 SERの実行時ライブラリは、タグの値を見て、 共用体のどのメンバが使用されているかを知り、 シリアライズやガベージコレクションに使用します。 タグに正しい値を入れておく(共用体との整合性を維持する) のはアプリケーションの責任です。


不完全型宣言

SERにおける「不完全型」とは、オペレーティングシステムや ウィンドウシステムなどで定義しているデータ型で、 スキーマファイルに記述する(せざるを得ない)ものを指します。

例えば、X-Windowには、GC(グラフィックコンテキスト)という データ型がありますが、図形描画ツールをSERを使用して開発した場合、 「図形」のデータ型中に、GCというメンバを持ちたくなるかもしれません。

そのような場合、

incomplete typedef GC;

このように宣言することにより、スキーマファイル中で "GC"という型を使用することができるようになります。 対応するCのコードは生成されません。

不完全型のデータは、transient指定された構造体のメンバとしてのみ使用できます。 不完全型は、名前を宣言しただけであり、その内容が何であるかについて SERは一切関知しません(できません)。

不完全型として宣言できるのは、スカラだけです(Cにおける基本型とポインタ)。 これは、不完全型に対応する初期化子として"0"を使用するという 乱暴な方針を採用したためです。


定数宣言

定数を宣言するには、以下のように記述します。

constant HOGE = 5;

このようにして宣言した定数は、 スキーマファイル中においてのみ有効です。 C言語側で使用することはできません。 スキーマファイル内で宣言された定数は、AP向けヘッダファイルでは、 即値に展開されています。

初期化には、定数式を使用することができます。

constant HOGE = 5 * (3 + 2);

デフォルト値の設定

それぞれのデータ型に、デフォルト値を設定することができます。 デフォルト値を設定すると、新しくメモリを確保したとき、 領域がその型のデフォルト値で初期化されます。

デフォルト値が設定できるのは、整数・実数 ・文字列(charへのポインタ)のみです。 文字列以外のポインタは、表記の方法がないので初期化できません。

なお、デフォルト値を設定しない場合、整数・実数はゼロ、 ポインタはNULLで初期化されています。


ひとつ上のページに戻る | prev(使い方) | next(スキーマファイルの例)