本博文通过一个简单的例子介绍如何使用Xtext 编写一个简单DSL 的语法。
建立一个DSL语言
假设我们要建立一个DSL 语言来描述一个会议的信息:最终的描述文本是这样的:
datatype String
datatype Bool
entity Session {
title: String
isTutorial : Bool
}
entity Conference {
name : String
attendees : Person*
speakers : Speaker*
}
entity Person {
name : String
}
entity Speaker extends Person {
sessions : Session*
}
下面我们来看看如何使用Xtext来设计这个DSL的语法 。
语法的开头两行
第一行是语法的描述
grammar org.eclipse.xtext.example.Domainmodel
with org.eclipse.xtext.common.Terminals
其中:
org.eclipse.xtext.example.Domainmodel 是语法的名称、
org.eclipse.xtext.common.Terminals 是语法的状态。表示该语法重用和覆盖了特定的语法。org.eclipse.xtext.common.Terminals是Xtext 的库语法。它预定义了大多数公用Terminal 规则,比如ID,STRGING 和INT 等等。
下一个语句
generate domainmodel "http://www.eclipse.org/xtext/example/Domainmodel"
指定从该语法导出的EMF Ecore 包。(Ecore package)
常用的符号
(no operator) | exactly one |
? | zero or one |
* | zero or more |
+ | one or more |
例如:
DomainModel :
Entity*;
一个Xtext 语法不仅描述语法规则,而且描述AST(_Abstract Syntax Tree 抽象语法树)的结构。通常,每个语法建立树上的一个对象。元素的类型可以在名称的后面使用returns 关键字。
DomainModel returns DomainModel: ...
也可以写成:
DomainModel : ...
为了连接多个不同的对象在一起,可以写成:
DomainModel :
(elements+=Entity)*;
赋值符号
feature=... | 相当于 setFeature(...) |
list+=... | 相当于 getList().add(...) |
condition?=... | 相当于 setCondition(true) |
例如:规则Entity 可以定义多个Feature
Entity :
'entity' name=ID '{'
(features+=Feature)*
'}';
下面再增加Extends 的语法描述:
Entity :
'entity' name=ID ('extends' superType=[Entity])? '{'
(features+=Feature)*
'}';
比较特别的是右边的('extends' superType=[Entity])? 短句。这是一个交叉引用在别的地方说明的Entity。所以在这里并不是指向某个语法规则,而是指向一个EClass(你认为是一个实例就好了)。
数据类型的定义
在我们的例子中,不仅定义了Entity ,而且定义了两种数据类型
DomainModel :
(elements+=Type)*;
Type:
DataType | Entity;
DataType:
'datatype' name=ID;
下一步我们继续要定义Feature 的语法。
Feature:
name=ID ':' type=TypeRef;
定义TypeRef
TypeRef :
referenced=[Type] (multi?='*')?;
terminal 规则
从计算机编译技术中我们知道,编译分成词法分析和语法分析两部分,词法分析主要是提取每一个单词,在编译技术中叫做tockens 。在Xtext 中,词法分析的规则称为Terminal rule。例如
terminal ID:
'^'?('a'..'z'|'A'..'Z'|'_')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;
terminal INT returns ecore::EInt:
('0'..'9')+;
完整的语法
于是,我们完成了完整的语法描述:
grammar org.eclipse.xtext.example.Domainmodel
with org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.eclipse.org/xtext/example/Domainmodel"
DomainModel :
(elements+=Type)*;
Type:
DataType | Entity;
DataType:
'datatype' name=ID;
Entity:
'entity' name=ID ('extends' superType=[Entity])? '{'
(features+=Feature)*
'}';
Feature:
name=ID ':' type=TypeRef;
TypeRef:
referenced=[Type] (multi?='*')?;
另一个例子:
语言格式
var int a
var int b
var int c
calc int x = a
calc int y = a + c
calc int z = a * a + b
Xtext 描述的语法
grammar expr.ExprDemo with org.eclipse.xtext.common.Terminals
generate exprDemo "http://www.ExprDemo.expr"
import "http://www.eclipse.org/emf/2002/Ecore" as ecore
Model:
elements+=Element*;
Element:
VarDecl | Formula;
VarDecl returns Symbol:
{VarDecl} "var" type=Type name=ID ";";
Type:
IntType | BoolType | FloatType;
IntType:
{IntType} "int";
BoolType:
{BoolType} "bool";
FloatType:
{FloatType} "float";
Formula:
"calc" type=Type name=ID "=" expr=Expr ";";
Expr:
Addition;
Addition returns Expression:
Multiplication ({Plus.left=current}"+" right=Multiplication)*;
Multiplication returns Expression:
Atomic ( {Multi.left=current} "*" right=Atomic)*;
Atomic returns Expression:
{SymbolRef} symbol=[Symbol|QID] |
{NumberLiteral} value=NUMBER;
terminal NUMBER returns ecore::EBigDecimal:
('0'..'9')* ('.' ('0'..'9')+)?;
QID: ID ("." ID)*;
小结
语言的形式化描述是比较令人晦涩的。好像也没有详细描述Xtext 规则的文章。只能从实例中慢慢摸索。