今回は、クロスリファレンスなどのXtextの面白い機能も見てみましょう。
grammar org.example.domainmodel.DomainModel with
org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.example.org/domainmodel/Domainmodel"
Domainmodel :
elements += Type*
;
Type:
DataType | Entity
;
DataType:
'datatype' name = ID
;
Entity:
'entity' name = ID ('extends' superType = [Entity])? '{'
features += Feature*
'}'
;
Feature:
many?='many'? name = ID ':' type = [Type]
;
org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.example.org/domainmodel/Domainmodel"
Domainmodel :
elements += Type*
;
Type:
DataType | Entity
;
DataType:
'datatype' name = ID
;
Entity:
'entity' name = ID ('extends' superType = [Entity])? '{'
features += Feature*
'}'
;
Feature:
many?='many'? name = ID ':' type = [Type]
;
選択は継承としてモデル化される
Type: DataType | Entity;
この場合、Typeルールが適用される場所では、DataTypeまたはEntityのいずれかのルールが適用されるというのがグラマー的な理解ですが、モデル的にはTypeはDataTypeとEntityの汎化として扱われます。つまり、基底型Typeを継承したDataType型とEntity型が生成されます。
DataTypeとEntityのいずれが選択されるかに曖昧さが生じないよう注意が必要です。DataTypeとEntityルールには、どちらも先頭にキーワード('datatype'や'entity')が定義されており、出現するキーワードによってルール選択の曖昧さが生じないようになっています。
もし構文に曖昧さがある場合、グラマーコンパイル時のエラーでそれが分かります。
オプション構文とキーワード
「?」は正規表現同様、0〜1回の出現、すなわちオプションであることを意味しています。キーワードを含む割当構文全体がオプションとなります。
この場合はオプション構文が1つしか無いので、'extends'キーワードが無くても曖昧さは生じませんが、複数のオプション構文がある場合は、何が省略されているのかが曖昧になるので、多くの場合オプション構文にキーワードが含まれる必要があります。
キーワードの有無自体のオプション構文もし構文に曖昧さがある場合、グラマーコンパイル時のエラーでそれが分かります。
Entity:この場合、'extends'キーワードとそれに続くsuperType=[Entity]の項はオプションです。
'entity' name = ID ('extends' superType = [Entity])? '{'
features += Feature*
'}'
;
「?」は正規表現同様、0〜1回の出現、すなわちオプションであることを意味しています。キーワードを含む割当構文全体がオプションとなります。
この場合はオプション構文が1つしか無いので、'extends'キーワードが無くても曖昧さは生じませんが、複数のオプション構文がある場合は、何が省略されているのかが曖昧になるので、多くの場合オプション構文にキーワードが含まれる必要があります。
Feature:
many?='many'? name = ID ':' type = [Type];
'many'キーワードの有無自体がモデル上の情報になるオプション構文です。
「?=」オペレータは、右辺の構文が現れた場合のみ左辺が真になる割当構文です。
「?=」オペレータは、右辺の構文が現れた場合のみ左辺が真になる割当構文です。
この時、Featureモデルのmany属性はboolean型(isMany())としてモデル化されます。
クロスリファレンスクロスリファレンスの仕組みは、Xtextの面白い強力な機能の一つです。DSL内でユーザが定義した識別子を、DSL内の別な箇所で参照する機能です。
例えば、プログラミング言語の型名や変数名のように、ユーザがDSL内のどこかで定義/宣言した識別子を、他所で指定することが多くありますが、このような場合に使用できるのが、このクロスリファレンスの機能です。
生成されるDSLエディタ上でも、他所で定義した識別子が、参照時にコンテンツアシストされるようになり、とても強力です。
例えば、プログラミング言語の型名や変数名のように、ユーザがDSL内のどこかで定義/宣言した識別子を、他所で指定することが多くありますが、このような場合に使用できるのが、このクロスリファレンスの機能です。
生成されるDSLエディタ上でも、他所で定義した識別子が、参照時にコンテンツアシストされるようになり、とても強力です。
'extends' superType = [Entity]
type = [Type]
このように右辺が [ルール] となっている割当構文は、クロスリファレンスになります。
このサンプルの例では、ユーザがDSL内で「型」を定義したあと、別な場所でその定義済みの「型名」を指定できるようにしています。
通常の割当構文では右辺でルール呼び出しした結果がフィールドに格納されるため包含参照になりますが、クロスリファレンスの場合のフィールドは非包含参照になります(他所で定義された値を参照するのですから当然ですが)。
(ここで言う包含参照とは、EMFにおけるcontainment=trueな参照モデルです)
このサンプルの例では、ユーザがDSL内で「型」を定義したあと、別な場所でその定義済みの「型名」を指定できるようにしています。
通常の割当構文では右辺でルール呼び出しした結果がフィールドに格納されるため包含参照になりますが、クロスリファレンスの場合のフィールドは非包含参照になります(他所で定義された値を参照するのですから当然ですが)。
(ここで言う包含参照とは、EMFにおけるcontainment=trueな参照モデルです)
クロスリファレンスの参照先の指定には、若干の注意が必要です。
- DSL上の参照は、参照先モデルの「name」属性と同じ値を、「ID」終端ルールにより記述することで行われます。
このルールに従わないと、クロスリファレンスは機能しません。
(変更することは可能です) - 参照先名称は、厳密にはルール名ではなくて、モデルのEClass名です。
Xtextではルール名とモデル名を別定義できるので、その場合は要注意です。
これらのサンプルテクニックを覚えると、ちょっとしたDSLをすぐに作れそうですよね。
どの構文も簡単ですし、どのようにモデル化されるかも分かりやすいと思います。
0 件のコメント:
コメントを投稿