Xtext(2.4.2)の文法のメモ。
|
|
xtextファイルの先頭に自分のDSLのクラス名を書く。
(この名前でJavaのクラス等が生成される)
grammar クラス名
既存の(他の)xtextに書かれた定義を利用したい場合は、withを使ってミックスインする。
(Scalaでtraitをミックスインするのと同様)
grammar クラス名 with 他定義
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
全体的な空白(コメント)の指定もgrammarで行う。
grammar クラス名 hidden(ルール名, …)
grammar jp.hishidama.xtext.dmdl_editor.DMDL hidden(WS, ML_COMMENT, SL_COMMENT)
Xtext自身の拡張定義?をインポートすることが出来る。
import URI as 名前
import "http://www.eclipse.org/emf/2002/Ecore" as ecore
例えばecoreの定義をインポートすると、terminalの指定が使えるようになる。
Xtextで書かれた内容は、EMF(ecoreファイル)に変換されるらしい。その為の宣言をする。
generate 名称 URI
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl" generate dmdl "http://www.hishidama.jp/xtext/dmdl_editor/DMDL"
このURIは、EMFで使用する識別子(名前)らしい。
インターネット上のそのURIの指す場所に何らかのファイル等が必要なわけではない。(だったら「http:
」とか付けるな、と言いたいが(苦笑))
ルール(文法)を定義する。
ルール名は、Javaのクラス名と同様に、CamelCaseとする。
ルール名 : ルール内容 ;
ルールに合致した場合、デフォルトではルール名と同じ名前のクラス(型)が作られることになる。
特定の型になるように指定するにはreturnsを付ける。
ルール名 returns 戻り型 : ルール内容 ;
QualifiedName returns ecore::EString : ID ('.' ID)* ;
プリミティブな型はecoreで定義されているようなので、それを使う。
ルールを空白区切りで並べると、全てが合致した場合だけ受け付けられる。
Greeting: 'Hello' ID '!';
シングルクォーテーションまたはダブルクォーテーションで囲んだ文字列は、その文字がその位置に来ることを示す。
「\r」「\n」や「\t」等で改行やタブ文字を表す。
縦棒「|
」で複数のルールの選択肢を並べる。
Type: DataType | Entity;
※角括弧で囲った「[Entity|STRING]
」という形式は、選択肢を並べているのではなく、リンク(cross reference)のルール定義。[2013-08-21]
ルールは丸括弧で囲んで優先度を高くすることが出来る。
ルールの後に適用回数を示す記号を付けることが出来る。
記号 | 例 | 説明 |
---|---|---|
なし | 'Hello' |
1回だけ適用する。 |
? |
'Hello'? |
0回または1回。(有るか無いかの指定) |
+ |
Greeting+ |
1回以上。 |
* |
Greeting* |
0回以上。 |
正規表現の「{4}
」のような、特定回数だけ適用させる方法は無いっぽい??
(後で使うために、)合致したルールを変数に入れることが出来る。
Greeting: 'Hello' name=ID '!';
「name=ID」は、IDのルールに合致した場合、nameという変数にその内容が入れられる。
上記のGreetingルールからは、以下の様なクラスが生成される。
public interface Greeting extends EObject { public String getName(); public void setName(String value); }
nameという変数名はちょっと特別扱いで、名前(ラベル)を表示する際に自動的に使われる。例えばデフォルトで生成されるアウトラインページにはnameの内容が表示される。
代入記号には以下のものがある。
記号 | 例 | 説明 |
---|---|---|
= |
name=ID |
合致したルールの内容を変数にセットする。 |
+= |
greetings+=Greeting*; |
複数のルールが許容されるときに、合致した各内容を変数に追加していく。(変数はリストになる) |
?= |
(many ?= 'many')? |
ルールに合致したら、変数にtrueをセットする。 (合致するものがあったかどうかのチェックを行いたい場合に使う) |
「*」を使って0個以上適用できるルールを作ろうとした場合、以下のような警告メッセージが出ることがある。[2014-07-28]
Properties: '{' (properties+=Property)* '}';
→「The rule 'Properties' may be consumed without object
instantiation. Add an action to ensure object creation, e.g. '{Properties}'.
」
適用できる要素(properties)が1個も無い場合、ルール自身(Properties)のオブジェクトも作られない、という意味だろうか。
この場合、波括弧でルール名を指定すると警告が消える。
Properties:
'{' {Properties} (properties+=Property)* '}';
波括弧ブロックはアクションを記述するもの。ルール名だけを記述すると、そのルールオブジェクトを作成するアクションになるようだ。
上記の例だと、「'{'
」の後ろにアクション(波括弧ブロック)があるので、'{'
が来たら無条件にPropertiesオブジェクトを生成することになるらしい。
参考: Eclipse Community ForumsのGrammar warnings(The rule may be consumed without object instantiation)
Xtext(Xtextが使用しているANTLRパーサー)では、左再帰(left recursive)を許容していない。
左再帰のルール定義を行うと、Xtextのビルドは成功するが、生成されたエディター上で入力を行ったときに固まる(無限ループに陥り、そのうちheap size不足でOutOfMemoryErrorになる)。
(yaccやbisonといったツールでは左再帰を記述できる(上手く扱ってくれるらしい)ので、それと同じ気分でXtextを記述するとハマる。)
左再帰は、例えば「要素の一覧」を定義するときの以下の様な記述方法。
MyList : MyList ',' Element | Element ;
「MyListの後ろにElementがある、もしくはElement 1個」という書き方。BNFでは結構おなじみ。
「MyList Element」のように、自分自身が左側に出てくるのがいけないらしい。
実際のテキストを解釈するときに左側のルールから適用しようとする為、MyListの再帰呼び出しで無限ループになる。
Xtextの場合は、適用回数を指定する書き方にするのが良さそう。
MyList : Element (',' Element)* ; MyList : list+=Element (',' list+=Element)* ; //変数に代入したい場合
詳しくは、Xtextのドキュメントの「left recursive」の部分を参照。
terminalを付けると、終端ルールの定義になる。
(terminalを使う為には、ecoreの定義をインポートしておく必要がある。org.eclipse.xtext.common.Terminalsをミックスインしていれば、自動的にインポートされる)
terminal ルール名 : ルール内容 ;
terminal WS: (' ' | '\t' | '\r' | '\n')+ ;
terminalの場合は、ルール名は全て大文字でアンダースコア「_」区切りのSNAKE_CASEにする。
通常のルールと終端ルールの違いは、yacc/lex(構文解析/字句解析)の違い。
つまり、終端ルールは字句レベルのルール。
終端ツールの補助的なルールを定義する際はfragmentを付ける。[2013-08-19]
terminal fragment ルール名 : ルール内容 ;
terminal STRING: '"' ('\\' ('b' | 't' | 'n' | 'f' | 'r' | '"' | '\\') | '\\u' HEX_CHAR HEX_CHAR HEX_CHAR HEX_CHAR | !('\\' | '"'))* '"'; terminal fragment HEX_CHAR: '0'..'9' | 'a'..'f' | 'A'..'F';
DSL上で選択できる文字列を定義する。
enum ルール名 : 列挙子名 | 列挙子名… ; enum ルール名 : 列挙子名="ラベル" | 列挙子名="ラベル"… ;
enum ChangeKind : ADD | MOVE | REMOVE; enum ChangeKind : ADD="add" | MOVE="move" | REMOVE="remove";
列挙子の定義はクォーテーションで囲ったりしないが、その文字列がそのままDSL上で入力できるようになる。
列挙子名の定義の後ろに、実際に入力できる文字列を指定することが出来る。[2013-09-02]
※列挙ルールから生成されるクラスは、enumになる。
XtextでDSLの文法を定義する際の個人的な指針のようなもの。[2013-08-19]