代码改变世界

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

2012-09-03 09:58  知平软件  阅读(1728)  评论(0编辑  收藏  举报

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

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

在上一篇文章中,简单介绍了iQuery解析器的词法分析部分,本文接着将语法分析部分解释完毕,阅读完本文后,应该可以将iQuery扩展到其他编程语言上。

下面是iQuery的完整语法(其实可以把它当作一个广义的正则表达式对待):
https://github.com/vowei/iQuery/blob/master/iQuery.g

antlr支持EBNF语法,也就是说它支持可选和重复的元素,如:

   1:  selectors
   2:      : multi_selectors multi_selectors*
   3:      ;

上面的语法表示selectors由一到多个multi_selectors组成,因为在EBNF语法里,没有类似正则表达式的“+”号操作符,所以使用“multi_selectors multi_selectors*”这种形式描述。

而:

   1:  selector 
   2:      : selector_expression
   3:          (
   4:              ('+' selector_expression)
   5:          |
   6:              ('~' selector_expression)
   7:          )?
   8:      ;

这个语法表示selector这个元素的组成部分,是selector_expression后面要么跟有“('+' selector_expression)”,要么跟有“('~' selector_expression)”,要么后面什么也不跟 -“?”号的作用。

在antlr里,语法定义的规则一般是,小写字母组成的单词是语法的组成部份,而标记(Token)都是由大写字母组成,如:

   1:  selector_expression
   2:      : atom
   3:      | ':' indexop '(' INTEGER ')'
   4:      | ':' NOT '(' selectors ')'
   5:      | ':' HAS '(' selectors ')'
   6:  | ':' CONTAINS '(' QUOTED_STRING ')'
   7:  ….
   8:  ;

上面的语法里,大写的字母例如“INTEGER”、“NOT”、“HAS”、“CONTAINS”和“QUOTED_STRING”都是标记(Token),这些标记都是从词法分析器解析完输入字符串之后产生的标记流中得到。而小写字母组成的单词例如“selector_expression”、“atom”、“indexop”和“selectors”都是语法的组成部份,这些元素的定义都可以在语法定义源文件中找到。

Antlr生成的语法解析器是一个LL(*)的编译器,LL类型的编译器是一个自顶向下进行语法分析的编译器,这种编译方式跟手写编译器进行语法解析的方式很像,因此相对于yacc等LR类型(自底向上)的编译器更容易理解。

比如说,以“#ABC”这个iQuery查询语句为例,antlr生成的解析器,也就是iQuery解释器碰到这个字符串以后,它依照一个自顶向下的顺序进行语法匹配:

1.    首先从“query”这个语法元素开始,由于“query”元素的两个子元素只有第一个“selectors NEWLINE* EOF”成功匹配(这是因为第二个元素的开头就是换行符,跟“#ABC”无法匹配),所以解析器再接着进入“selectors”的定义尝试匹配。
2.    “selectors”的定义里“multi_selectors”也仅仅是一个语法元素,里面没有任何词法标记(Token),因此解析器递归向下匹配语法元素,匹配顺序如下:

query -> selectors -> multi_selectors -> selector -> selector_expression
3.    当匹配到selector_expression时,因为其可选的语法子句里有词法标记,而且“# ELEMENT”可以完全匹配“#ABC”这个输入字符串,这样在selector_expression处就成功匹配了输入字符串,并且“selector_expression”没有更多的语法规则需要输入字符串匹配,因此解析器退出“selector_expression”这个语法规则,发现上一层规则“selector”也没有更多的语法规则需要匹配,递归上溯,直到“query”规则。
4.    在“query”规则里,匹配完“selector”子规则后,后一个是可选的换行符 - “NEWLINE*”,由于输入字符串中没有换行符,所以跳过这个规则,碰到最后一个规则“EOF”-表示字符串或者文件的结尾。因为在“selector_expression”里已经由“’#’ ELEMENT”匹配完整个字符串了,没有更多的字符留下。到这里,语法解析器就认为执行了一次成功的匹配,而输入字符串“#ABC”是一个合法的输入。


比如说,针对“> :first [‘value’]”这个查询,iQuery解析器依照自顶向下的方式进行匹配:

1.    首先匹配字符“>”,匹配顺序是“query”-> “selectors” -> “multi_selectors”-> “'>’ selector”。
2.    匹配到“'>’ selector”这个规则时,因为“'>’”后面必须跟一个满足“selector”规则的字符串。
3.    解析器继续用“:first [‘value’]”试图匹配“selector”这个规则,这时的匹配顺序是“selector” -> “selector_expression”-> “’:’ FIRST”。
4.    “’:’ FIRST”这个规则消化掉“:first”字符串,由于输入的iQuery字符串还剩下“[‘value’]”,而“selector_expression”规则已经没有多余的子规则了,解析器上溯,上溯的顺序是:“selector_expression” -> “'>' selector”-> “multi_selectors”-> “selectors”。
5.    在“selectors”这个语法规则里,前面的匹配步骤只消化掉“multi_selectors multi_selectors*”里的第一个规则“multi_selectors”,还剩下第二个规则“multi_selectors*”没有匹配,因此解析器使用输入字符串剩下的字符匹配第二个规则。
6.    匹配的顺序是:“multi_selectors” -> “selector” -> “multi_attributes” -> “'['”。
7.    在这次匹配过程中,由于剩下的字符串是“[‘value’]”(注意value周边的单引号),没有任何一个“multi_attributes”的子规则匹配这段字符串,而且也无法回溯,因此一个语法错误发生了,解析器会抛出一个语法错误的异常信息,这个异常信息有点晦涩,需要做二次处理才能让iQuery使用者明白语法错误原因 – 语法错误的处理在后文会讲到。

从上面关于语法匹配的描述可以看出,这个过程跟一个函数递归调用非常类似,实际上,对于语法定义文件中的每一个语法规则(例如“query”、“multi_selectors”等规则),antlr都会为其生成一个函数调用(例如函数query()、multi_selectors()等,而且antlr还提供了给生成的函数传入参数,设置和获取函数返回值的手段,参数的声明语法跟指定语言的语法是一致的。

好了,语法方面的解释就暂时写到这里,下文讲解Java版和JavaScript版的iQuery解析器的具体实现。

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