S-JIS[2013-08-17/2014-07-28] 変更履歴

Xtext文法

Xtext(2.4.2)の文法のメモ。


クラス名の定義

xtextファイルの先頭に自分のDSLのクラス名を書く。
(この名前でJavaのクラス等が生成される)

grammar クラス名

既存の(他の)xtextに書かれた定義を利用したい場合は、withを使ってミックスインする。
Scalatraitミックスインするのと同様)

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]


Xtext目次へ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま