扩展iQuery使其支持多种编程语言(三) – 兼编译器的语义分析简介
2012-09-24 16:20 知平软件 阅读(1426) 评论(0) 收藏 举报iQuery是一个开源的自动化测试框架项目,有兴趣的朋友可以在这里下载:https://github.com/vowei/iQuery/downloads
源码位置:https://github.com/vowei/iQuery
相关的使用文档,请参看:
- 开源类库iQuery Android版使用说明
- 类jQuery selector的控件查询iQuery开源类库介绍
- 开源手机自动化测试框架iQuery入门教程(一)
- 开源手机自动化测试框架iQuery入门教程(二)
- 开源手机自动化测试框架iQuery入门教程(三)
在上一篇文章中,简单介绍了iQuery解释器的语义分析部分。
ANTLR使用的LL(*)的语法解析技术,它在从语法文件生成编译器时,会将每一个语法元素生成为一个函数,为了在语法元素之间传递数据,ANTLR支持函数调用和参数传递的概念,比如(摘自:https://github.com/vowei/iQuery/blob/master/java/iquery/iquery-core/src/main/java/cc/iqa/iquery/iQuery.g):
query [List<ITreeNode> candidates] returns [List<ITreeNode> survival]
: selectors[$candidates] NEWLINE* EOF
;
selectors [List<ITreeNode> candidates] returns [List<ITreeNode> survival]
在上面的语法里,“query [List<ITreeNode> candidates]”就是在antlr里声明参数的方式,因为antlr默认是生成Java源码,所以参数声明的方式也是遵循Java语法的。“returns [List<ITreeNode> survival]”指明了生成的query函数的返回值,返回值的类型“List<ITreeNode>”和保存返回值的局部变量名“survival”。而第2行里,“selectors[$candidates]”则是语法推演,在推演过程中,antlr使用类似函数调用的概念,允许上级语法元素传递参数给下级语法,而“selector”的函数形式可以参看第5行。
在语法文件里填充好函数声明和函数之间的调用关系后,可以使用下面这个命令生成解释器的源码:
java -cp antlr-3.3-complete.jar org.antlr.Tool iQuery.g
其中antlr-3.3-complete.jar可以从antlr的官网上下载。需要注意的是,当前写作时的最新版antlr-3.4,对生成JavaScript语言的解释器有Bug,因此建议使用3.3版本。
代码生成后,上例中的语法就会被翻译成类似下面的代码:
// 下面一行对应代码:
// query [List<ITreeNode> candidates]
public final List<ITreeNode> query(List<ITreeNode> candidates) throws RecognitionException {
... ...
// 下面代码对应:selectors[$candidates]
selectors1=selectors(candidates);
... ...
// 对应代码:returns [List<ITreeNode> survival]
return survival;
}
public final iQueryParser.selectors_return selectors(List<ITreeNode> candidates) throws RecognitionException {
... ...
}
相应的,如果是要生成JavaScript版本的解释器 – 可用在iOS上,只需要在语法文件的顶部,加上一个选项指示(参考代码 - https://github.com/vowei/iQuery/blob/master/iOS/lib/iQuery.g):
options {
language=JavaScript;
}
对应的参数声明和参数传递使用JavaScript语法填充:
prog [candidates] returns [survival]
: p=selectors[$candidates] NEWLINE* EOF
;
selectors [candidates] returns [survival]
对比Java版和JavaScript版的语法,可以看到语法之间传递数据的方式在antlr里是固定的 - 参看“selectors[$candidates]”。
定义好参数列表和函数之间的调用关系之后,所需要做的就是填充自定义的过滤代码,根据不同的语法元素所代表的语义来过滤候选控件集合(candidates)。
例如,下面的代码中就是实现“:first-child”的语义,从候选控件集合中过滤出第一个子控件并返回:
| ':' FIRST_CHILD
{
List<ITreeNode> nodes = new ArrayList<ITreeNode>();
for ( int i = 0; i < $candidates.size(); ++i ) {
ITreeNode node = $candidates.get(i);
if ( node.getChildren().size() > 0 ) {
nodes.add(node.getChildren().get(0));
}
}
$survival = nodes;
}
上面的代码有几个地方需要留意,首先自定义的代码是用大括号“{”括起来的,参见第2-12行,antlr直接将里面的代码插入到生成的编译器代码的指定位置。另外,不需要在代码里显式使用“return”跳出函数,而是给预先定义的返回值变量赋值 - “ $survival = nodes;”。例如上面的代码最终会生成:
switch ( input.LA(2) ) {
... ...
case FIRST_CHILD:
{
alt9=7;
}
break;
... ...
case 7 :
// cc/iqa/iquery/iQuery.g:674:7: ':' FIRST_CHILD
{
match(input,37,FOLLOW_37_in_selector_expression884);
match(input,FIRST_CHILD,FOLLOW_FIRST_CHILD_in_selector_expression886);
List<ITreeNode> nodes = new ArrayList<ITreeNode>();
for ( int i = 0; i < candidates.size(); ++i ) {
ITreeNode node = candidates.get(i);
if ( node.getChildren().size() > 0 ) {
nodes.add(node.getChildren().get(0));
}
}
survival = nodes;
}
break;
case 8 :
由于antlr为每一个语法元素生成一个函数,因此可以使用一些小的编程技巧,比如为了实现“>>”和“>”这样的子孙节点操作符,这些操作符后面可能还会跟随有过滤条件,例如“>> :first”这个查询语句的意思就是,在当前候选控件集合里,取第一个控件的所有子孙节点,并返回子孙节点集合里的第一个元素。而iQuery.g这个语法是无状态的,需要在操作符“>>”和过滤条件“:first”之间传递子孙节点集合,因此在源码里是这样写的:
| DESCENDANT c=selector[descendants($candidates, -1)]
{
$survival = $c.survival;
}
注意上例中“selector[descendants($candidates, -1)]”,在调用“selector”这个语法元素之前,调用了函数“descendants”,目的就是获取当前“candidates”控件集合里的子孙控件之后再传递给“selector”语法。而变量“c”则是antlr的语法糖,用来保留“selector”语法元素过滤后返回的控件集合。
好了,本文对iQuery的语义分析的介绍就讲到这里,下一篇文章讲解iQuery的错误处理。
浙公网安备 33010602011771号