DSL的实现要点(2)
实现外部DSL
与上一篇中所提及内部DSL不同,使用者不是通过API调用来使用DSL,而是通过我们定义的特定语法的领域语言来使用DSL。
1 XML形式的DSL
脚本文件
<process name="Auto-Door">
<state name="Open">
<transition event="time-out" next_state="Close"/>
</state>
<state name="Close">
<transition event="people-closer" next_state="Open"/>
</state>
</process>
实现
publicclass XmlConfigParser {
//followings are context variables
private Machine currentMachine;
private State currentState;
class ElementHandler extends DefaultHandler{
private String getAttributeValue(String elemName,String attributeName,Attributes attris){
String attrValue=attris.getValue(attributeName);
if (attrValue==null){
thrownew XmlConfigParseException("Element "+elemName+" shoudle have the attribute:"+attributeName);
}
return attrValue;
}
@Override
publicvoid endElement(String arg0, String arg1, String elemName)
throws SAXException {
if (elemName.equals("state")){
currentMachine.getStates().add(currentState);
}
}
@Override
publicvoid startElement(String arg0, String arg1, String elemName,
Attributes attris) throws SAXException{
if (elemName.equals("process")){
String processName=getAttributeValue(elemName,"name",attris);
currentMachine=new Machine(processName);
}
if (elemName.equals("state")){
String stateName=getAttributeValue(elemName,"name",attris);
currentState=new State(stateName);
}
if (elemName.equals("transition")){
String eventName=getAttributeValue(elemName,"event",attris);
String nextState=getAttributeValue(elemName,"next_state",attris);
Transition transition=new Transition();
transition.setEvent(new Event(eventName));
transition.setNextState(nextState);
currentState.getTransitions().add(transition);
}
}
}
public Machine parser(String fileName){
SAXParserFactory spfactory =
SAXParserFactory.newInstance();
try{
SAXParser saxParser =
spfactory.newSAXParser();
XMLReader reader=saxParser.getXMLReader();
reader.setContentHandler(new ElementHandler());
reader.parse(fileName);
returncurrentMachine;
}catch(Exception e){
thrownew XmlConfigParseException("parsing is failed",e);
}
}
}
实现要点
上述实现是通过SAX来进行XML解析的。
1 将领域模型结构直接映射为XML元素的结构
我们用这种方式来设计我们的DSL,这样做的好处是DSL比较容易使用(更接近领域模型),同时解析程序也会相对简单,比较容易生成相应的语义模型。
2 使用上下文变量
如上面程序中的:
private Machine currentMachine;
private State currentState;
他们就是上下文变量,由于SAX是顺序解析的,所以必须保持正确的工作上下文,如把生产Transition对象加入到正确的State中。
2 自定义语言
脚本文件
Machine (Auto-Door){
State(Open){
Transition{
event : time-out ,
next-state : Close
}
}
State (Close){
Transition{
event : people-closer ,
next-state : Open
}
}
}
实现
自己设计语法并实现解析器,通常需要我们具备一定的编译原理知识并且借用一定的解析器生成工具来帮助我们生产解析器代码。
实现中本人使用了 Antlr
Antlr的语法描述文件:
grammar StateMachineG;
@header {
import org.ccsoft.statemachine.models.Machine;
import org.ccsoft.statemachine.models.State;
import org.ccsoft.statemachine.models.Transition;
import org.ccsoft.statemachine.models.Event;
}
@members {
public void emitErrorMessage(String msg) {
throw new RuntimeException(msg);
//super.emitErrorMessage(msg);
}
}
machine returns [Machine value] : 'Machine''('NAME')''{'{$value=new Machine($NAME.text);} (e=state{$value.getStates().add($e.value);})+'}';
state returns [State value] : 'State''('NAME')''{'{$value=new State($NAME.text);}(e=transition{$value.getTransitions().add($e.value);})+'}';
transition returns [Transition value]
: 'Transition''{'{$value=new Transition();}e=event{$value.setEvent($e.value);}','f=nextState{$value.setNextState($f.value);}'}';
event returns [Event value] : 'event'':'e=NAME{$value=new Event($NAME.text);};
nextState returns [String value]
: 'next-state'':'e=NAME{$value=$NAME.text;};
NAME : ('a'..'z' |'A'..'Z'|'0'..'9')+ ;
WS : (' ' |'"t' |'"n' |'"r' )+ {skip();} ;
实现要点
1 采用Antlr的内嵌Action
对于DSL的通常应用即通过外部脚本生产相关部分语义模型对象,使用Antlr的内嵌Action比采用语法树方式简单得多。