Capella -- AST generation utility

capellaは、定義ファイルを読み込んで、それに対応するASTを定義するようなデータ構造をC++ソースで出力するユーティリティです。

caperと一緒に使用されることを意図して作られたものですが、出力するソースはcaperには依存しません。STL(オプションによってはboostも)に依存します。

おまけ程度のデキなのでそのつもりで一つよろしくお願いします(もちろん間違いを指摘していただける分には大歓迎ですが)。

入力ファイル

入力ファイルは以下のような形になります。

atom = Identifier;

class-header = {
}

class-footer = {
    template < class Visitor >
    void accept( Visitor& visitor )
    {
        visitor.visit( *this );
    }
}

type Module = Declarations(declarations);
type Declarations = Declaration*(elements);
type Declaration = AtomDef | TypeDef;
type AtomDef = Atoms(atoms);
type Atoms = Identifier*(elements);
type TypeDef = Identifier(name), TypeDefRight(right);
type TypeDefRight = Scalor | List | Variant;
type Scalor = Identifier(stype);
type List = Identifier(etype);
type Variant = Identifier*(choises);

順に見ていきましょう。


atom = Identifier;

atom(それ以上分解できない要素)を定義します。scannerから渡ってくるデータなどになるでしょう。


class-header = {
	...
}

class-footer = {
	...
}

出力されるすべてのAST定義構造体の頭部/末尾に共通に挿入されるテキストです。字面的には、中かっこの対応程度しか見ませんので、コメントなどを入れることもできます。。


type Module = Declarations(declarations);
type Declarations = Declaration*(elements);
type Declaration = AtomDef | TypeDef;
type AtomDef = Atoms(atoms);
type Atoms = Identifier*(elements);
type TypeDef = Identifier(name), TypeDefRight(right);
type TypeDefRight = Scalor | List | Variant;
type Scalor = Identifier(stype);
type List = Identifier(etype);
type Variant = Identifier*(choises);

ASTの木構造を表現するデータ定義です。次節で説明します。

AST定義

AST定義は「type」宣言を用いて行います。type宣言には大きく分けて2通りあります。

フィールド宣言

[ type 構造体名 = フィールド型(フィールド名) ] という宣言では、ターゲットの構造体のフィールド定義が行われます。


たとえば、

type Module = Declarations(declarations);

このような宣言では、以下のような構造体が出力されます(詳細は省略、以下同じ)。

struct Module {
    Declarations* declarations;
};

複数のフィールドを持つ場合は、以下のようにします。

type TypeDef = Identifier(name), TypeDefRight(right);

出力はこのようになります。

struct TypeDef {
    Identifier* name;
    TypeDefRight* right;
};

各フィールドがコンテナになる(同じフィールドを複数所持できる)ようにしたい場合、型名の後に"*"をつけることで、その型はフィールドはコンテナになります。

type Atoms = Identifier*(elements);

出力はこのようになります。

struct Atoms {
    std::vector < Identifier* > elements;
};

多態宣言

[ type 基底構造体名 = 派生構造体名1 | 派生構造体名2; ] という形の宣言では、構造体の継承構造が構築されます。


たとえば、

type AtomDef = Atoms(atoms);
type TypeDef = Identifier(name), TypeDefRight(right);

という宣言があるとき、これをそのまま変換すると

struct AtomDef {
    Atoms* atoms;
};
struct TypeDef {
    Identifier* name;
    TypeDefRight* right;
};

という出力が生成されますが、これに加えて

type Declaration = AtomDef | TypeDef;

という宣言を加えると、AtomDef/TypeDefに共通の基底クラス "Declaration" が挿入され、

struct Declaration {};
struct AtomDef : public Declaration {
    Atoms* atoms;
};
struct TypeDef : public Declaration {
    Identifier* name;
    TypeDefRight* right;
};

このような出力になります。

オプション

デフォルト以外はあまりデバッグしてないのであてになりません。

-c++
通常のC++ソースを出力します(デフォルト)。
-c++-shared
boost::shared_ptrを用いたコードを出力します
-c++-variant
boost::variantを用いたコードを出力します。
-c++-stub
-c++-shared-stub
-c++-variant-stub
dot
graphvizのdot形式で継承グラフを出力します。

移植について

capella自体を他の言語に移植する場合、内部でboost.graphのトポロジカルソートをしれっと使っていたりするので、グラフライブラリの整備されていない言語だとかなりめんどくさかったりするかもしれません。

終わりに

結局のところ、デフォルトで使い手にとってしっくりするソースを出力できるとは限らないと思うので、てきとうにジェネレータのコード(capella_generate_cppあたり)を書き換えて使うのがいいと思います。ジェネレータのコードだけならばさほど難しいことはないと思います。