• 扩展iQuery使其支持多种编程语言(三) – 兼编译器的语义分析简介


    iQuery是一个开源的自动化测试框架项目,有兴趣的朋友可以在这里下载:https://github.com/vowei/iQuery/downloads

    源码位置:https://github.com/vowei/iQuery

    相关的使用文档,请参看:

    1. 开源类库iQuery Android版使用说明 
    2. 类jQuery selector的控件查询iQuery开源类库介绍 
    3. 开源手机自动化测试框架iQuery入门教程(一)
    4. 开源手机自动化测试框架iQuery入门教程(二) 
    5. 开源手机自动化测试框架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的错误处理。

    本文由知平软件 施懿民编写,请关注我们的微博

  • 相关阅读:
    莫队专题
    AJAX XML 实例
    AJAX 简介
    AJAX 服务器响应
    AJAX 创建XMLHttpRequest 对象
    AJAX 教程
    AJAX 向服务器发送请求
    AJAX onreadystatechange 事件
    AJAX ASP/PHP 请求实例
    让卖场的死角“起死回生”
  • 原文地址:https://www.cnblogs.com/vowei/p/2700243.html
Copyright © 2020-2023  润新知