JAVACC 入门(转载)

来源:互联网 发布:hp1000扫描仪安装软件 编辑:程序博客网 时间:2024/04/29 23:01

JAVACC 入门(转载)

JavaFlashIDEA算法Unix 
 

读了JavaCC自带文档中的SimpleExamples之后,有一点心得,于是总结一下,以备遗忘。

 

JavaCC的输入文档是一个词法和语法的规范文件,其中也包括一些动作的描述,它的后缀应该是jj。

 

简而言之,一个jj文档由下面几个部分构成:

l         Options{}部分:这个部分对产生的语法分析器的特性进行说明,例如向前看的token的个数(用来解除冲突)。这一部分是可以省略的,因为每一个选项都有默认值,当我们没有对某个选项进行说明时,它就采用默认值。也可以把这些选项作为javacc命令的参数来启动javacc,可以达到同样的效果。

l         分析器类的声明:这个部分指定了分析器类的名字,以及其他类中成员的声明。这个部分是必须有的。这个部分的声明如下:

PARSER_BEGIN(classname)

Class classname {

       ……

}

PARSER_END(classname)

l         词法部分声明:这里面有四类:SKIP、TOKEN、SPECIAL_TOKEN、MORE。其中,SKIP用来说明被忽略的串,下面是一个例子:

SKIP {

       “ “

|

       “\n”

|

       “\r”

}

TOKEN用来说明在词法层次上识别的token,下面是一个例子:

TOKEN {

       <ID: (“a”-“z”|”A”-“Z”)+>

|

       <NUM: (“0”-“9”)+>

}

       这个部分是可以省略的。

在词法声明部分,以#开头的token只是在词法分析时使用,不能作为语法分析的输入,也就是说,它相对词法分析是局部的。

l         语法声明和动作代码:这一部分生成的代码会直接插入分析器类声明的结束括号之前。一般而言,语法中的每一个非终结符都对应一个函数,其中函数的形式如下:

Return_type function_name()

{     变量声明和一些初始化的动作

}

{

       上下文无关文法的右部分,其中每个组成部分的形式如下:

       语法部分 {动作部分}

两个部分都可以省略。语法部分可以是一个字符串(简单的token常常可以这样处理),TOKEN中声明的token,或一个对某个非终结符对应的函数的调用。

}

 

以上说明的是jj文件的组成部分,下面再说明一下jj文件中语法的表示方法。Javacc中的语法表示吸收了UNIX中正规文法的一些记号,下面是一些:

l         []:其中的内容是可选的。

l         +:前面的内容出现一次或多次。

l         -:前后构成的闭区间。

l         *: 前面的内容出现0次或多次。

l         ?:前面的内容出现0次或一次。

l         ~:后面的内容的补。

l         |:前面或后面。

l         ():改变运算的优先级,把其中的内容作为一个整体。

 

很遗憾的是,javacc有一些问题,即它采用的是自顶向下的分析方法,而且没有回溯功能,因此如何解决冲突的问题,是程序员的责任。Javacc产生的程序采用的分析方法既不是纯正的递归下降算法(可以回溯),也不是纯正的LL算法,事实上,它和二者既有联系又有区别。

 

与递归下降算法相比,它们的共同点是都采用了函数来表示对应的非终结符,通过函数调用来表示非终结符之间的组成关系。二者之间的不同点在于javacc没有回溯能力,也就是说,如何展开非终结符在编译时就已经确定了。

 

与LL算法算法相比,它们的共同点是如何展开非终结符都是静态确定的(javacc是函数调用,LL是展开表),事实上,采用缺省选项的javacc的语言是LL(1)的语言。不同之处在于,前者采用的是函数调用,后者采用的是栈上展开的方法。

 

因为javacc的基础是自顶向下的分析方法,所以必须要解决的下面的两个问题:左递归和公因子。关于左递归的问题,一般采用经典的修改文法的方法解决。关于公因子的问题,可以改写算法(提取公因子),也可以通过展望(look ahead)更多符号的方法来解决。但是因为javacc扩展了经典的BNF,因此它面临更多的问题。总之,当在编译时碰到冲突问题时,就说到了一个choice point。

 

可以将javacc中choice point归结为以下几类:

l         由选择算子|引入的冲突,

l         由可省略算子[]或?引入的冲突

l         由重复算子*引入的冲突

l         由重复算子+引入的冲突

 

当在javacc中碰到冲突时,可以采用下面的方法之一解决问题:

l         修改语法,使之成为LL(1)语法。这个方法有一个问题:修改后的语法可能非常不直观了L

l         在jj文件中给javacc一些提示。这些提示可以根据展望的作用域分成全局提示和部分提示。要采用全局提示,只要将LOOKAHEAD=k写到Options块中即可(当然也可以写到javacc的命令行里),这样javacc产生的分析器就可以分析LL(K)语法生成的语言。也可以设置一个局部的LOOKAHEAD,这样既保持了LL(1)的高效性,又可以解决choice point中的。LOOKAHEAD的局部声明形式是LOOKAHEAD(…),其中括号中的可以是数字(说明向后看多少个记号),或者是一个表示文法的串(当成功时选择)。LOOKAHEAD的完整形式为LOOKAHEAD(amount, expansion, {java语言里的逻辑表达式}),它的含义是,如果到达amount个记号后,expansion如果仍然成立,且逻辑表达式也成立,则执行下面的展开。

 

采用第一种方法的好处是效率非常高,对机器友好,易于维护,采用第二种方法的好处是语法更加直观,但是却不易维护。有时候采用第一种方法是无法解决冲突的,第二种方法是唯一的选择。

0 0
原创粉丝点击