1、前序
真是书到用时方恨少啊,在大学的时候,虽然学过编译原理,但当时真是不懂啊,只是为了应付考试,死记硬背了一点点。现在呢,由于工作上的需要,不得不弥补一下啊。 这两天把编译原理的书又看了一遍,其实也就是主要看了文法,词法分析,语法分析而已,为了备忘,赶紧先记一下吧。
2、定义
词法分析,就是把源码中的一行行代码按照事先规定好的格式分隔成一个个单词符号(token),比如数字,变量名称,函数等等。
语法分析呢,主要就是分析词法分析后的一个个token,是否能够拼装,组成事先规定好的语法中的一个。
文法呢,就是对上面语法的规定的集合(程序里面用到的主要是上下文无关文法)。
3、实现
3.1词法分析
主要用到的技术有正则表达式,状态转换图,DFA(确定有限自动机),NFA(非确定有限自动机)以及超前搜索。
3.2语法分析
主要分自上而下分析和子下而上分析。
3.2.1自上而下分析
主要面临的问题有左递归和左因子。
3.2.1.1左递归的消除
可以参考一下公式
P->Pα|β可以转换成一下语句
P->βP‘
P‘->αP‘|ε (ε为空子)
3.2.1.2消除回溯,提取公因子
可以参考一下公式
A->δβ1|δβ2|δβ3|γ1|γ2|γ3
转换成
A->δA‘|γ1|γ2|γ3
A‘->β1|β2|β3
3.2.1.3 LL(1)文法
意思就是从左向右扫描输入串,最左推导,每一步只需向前看一个符号。
3.2.1.4实现方法
当一个文法满足 LL(1)文法条件时,我们就可以为它构造一个不带回溯的自上而下分析程序,这个分析程序是由一组递归过程组成的,每个过程对应文法的一个非终结符。这样的一个分析程序称为递归下降分析器。
另外一种实现方法为预测分析程序。就是使用一张分析表和一个栈进行联合控制。
3.2.2自下而上分析
从输入串开始,逐步进行规约,直至规约到文法的开始符号。
由于与我下文说的javacc没有关系,因此我就不再叙述了。
4、javacc概述
JavaCC是一个词法分析器和语法分析器的生成器,用于java领域,根据指定格式的输入文件生成java源码的词法、语法分析程序(比lex和yacc要人性化)。
是一个免费的开源的工具,可以从网上下载。
javacc采用的是自顶向下的分析方法,而且没有回溯功能,因此如何解决冲突的问题,是程序员的责任。
必须要解决两个问题:左递归和公因子
关于左递归的问题,一般采用经典的修改文法的方法解决。
关于公因子的问题,可以改写算法(提取公因子),也可以通过展望(look ahead)更多符号的方法来解决。 但是因为javacc扩展了经典的BNF,因此它面临更多的问题。总之,当在编译时碰到冲突问题时,就说到了一个choice point。
可以将javacc中choice point归结为以下几类:
l . 由选择算子 | 引入的冲突,
2. 由可省略算子 [] 或 ?引入的冲突
3. 由重复算子 * 引入的冲突
4. 由重复算子 + 引入的冲突
当在javacc中碰到冲突时,可以采用下面的方法之一解决问题
1.修改语法,使之成为LL(1)语法。
2.只要将LOOKAHEAD=k写到Options块中即可,javacc产生的分析器就可以分析LL(K)语法生成的语言
采用第一种方法的好处是效率非常高,易于维护。采用第二种方法的好处是语法更加直观,但是却不易维护。有时候采用第一种方法是无法解决冲突的,第二种方法是唯一的选择。
5、javacc入门
首先先从官网上面下载相应的javacc文件,我这里下载的是javacc-5.0.zip。里面包含一些入门文档(英文)和例子。
addr.jj
expression: Start->NUMBER(PLUS Primary)*
options { STATIC = false ; //生成非静态类 LOOKAHEAD=2;//向前看2个字母,消除歧义用的 DEBUG_PARSER = true;//以debug形式生成,便于调试 } PARSER_BEGIN(Adder) class Adder { public static void main( String[] args ) throws ParseException, TokenMgrError { Adder parser = new Adder( System.in ) ; parser.Start() ; } } PARSER_END(Adder) SKIP : { " " } SKIP : { "\n" | "\r" | "\r\n" } TOKEN : { < PLUS : "+" > } TOKEN : { < NUMBER : <DIGITS> |<DIGITS> "." <DIGITS> |<DIGITS> "." | "." <DIGITS>> } TOKEN : { < #DIGITS : (["0"-"9"])+ > } void Start() : {} { <NUMBER> ( <PLUS> <NUMBER> )* <EOF> }
把上面代码保存到javacc bin目录下,执行以下语句。
相信能够看懂怎么使用吧。
6、javacc与eclipse整合
从网上下载easy-javacc-1.5.7-eclipse plugin.exe,并安装到eclipse的安装目录里面。然后重启eclipse,看下图。
编译adder.jj,生成java源文件(词法语法分析程序)
7、扩展语法分析器
在上面的例子中的start函数中,我们仅仅通过语法分析器来判断输入的语句是否正确。
我们可以扩展BNF表达式,加入Java代码,使得经过语法分析后,得到我们想要的结果或者对象。
我们将start函数改写为:
注意:t.kind表示单词的类别,t.image表示单词值。
8、javacc实例一(计算器)
expression:
Start -> ( Expression EOL )* <EOF> Expression -> Term(PLUS Term|MINUS Term)* Term -> Primary(TIMES Primary | DIVIDE Primary)* Primary -> NUMBER
Calculator.jj
options { static = false; } PARSER_BEGIN(Calculator) import java.io.PrintStream ; public class Calculator { double previousValue = 0.0 ; public static void main( String[] args )throws ParseException, TokenMgrError, NumberFormatException { Calculator parser = new Calculator( System.in ) ; parser.Start( System.out ) ; } } PARSER_END(Calculator) SKIP:{ " " } TOKEN:{< EOL : "\n"|"\r"|"\r\n" >} TOKEN:{< PLUS : "+">} TOKEN :{ < MINUS : "-" > } TOKEN:{< TIMES : "*" > } TOKEN:{< DIVIDE : "/" > } TOKEN:{< NUMBER : <DIGITS>| <DIGITS>"." <DIGITS> | <DIGITS>"."|"." <DIGITS> >} TOKEN : {< #DIGITS : (["0"-"9"])+ >} void Start(PrintStream printStream) throws NumberFormatException : {} { ( previousValue = Expression() <EOL> { printStream.println(previousValue); } )* <EOF> } double Expression() throws NumberFormatException : { double i; double value; } { value = Term() ( <PLUS> i = Term() { value += i;} | <MINUS> i = Term() { value -= i; } )* { return value; } } double Term() throws NumberFormatException : { double i; double value; } { value = Primary() ( <TIMES> i = Primary () { value *= i;} | <DIVIDE> i = Primary () { value /= i; } )* { return value; } } double Primary() throws NumberFormatException : { Token t; double d; } { t = <NUMBER> { return Double.parseDouble( t.image ); } }
9、javacc实例一(计算器扩展)
在8的基础上,增加括号支持和负数计算
expression:
Start -> ( Expression EOL )* <EOF> Expression -> Term(PLUS Term|MINUS Term)* Term -> Primary(TIMES Primary | DIVIDE Primary)* Primary -> NUMBER | PREVIOUS | MINUS Primary | OPEN_PAR Expression CLOSE_PAR
Calculator.jj
options
{
static = false;
}
PARSER_BEGIN(Calculator)
import java.io.PrintStream ;
public class Calculator {
double previousValue = 0.0 ;
public static void main( String[] args )throws ParseException, TokenMgrError, NumberFormatException {
Calculator parser = new Calculator( System.in ) ;
parser.Start( System.out ) ;
}
}
PARSER_END(Calculator)
SKIP:{ " " }
TOKEN:{< EOL : "\n"|"\r"|"\r\n" >}
TOKEN:{< PLUS : "+">}
TOKEN :{ < MINUS : "-" > }
TOKEN:{< TIMES : "*" > }
TOKEN:{< DIVIDE : "/" > }
TOKEN:{< OPEN_PAR : "(" > }
TOKEN:{< CLOSE_PAR : ")" > }
TOKEN:{< PREVIOUS : "$" > }
TOKEN:{< NUMBER : <DIGITS>| <DIGITS>"." <DIGITS> | <DIGITS>"."|"." <DIGITS> >}
TOKEN : {< #DIGITS : (["0"-"9"])+ >}
void Start(PrintStream printStream) throws NumberFormatException :
{}
{
(
previousValue = Expression()
<EOL>
{ printStream.println(previousValue); }
)*
<EOF>
}
double Expression() throws NumberFormatException :
{
double i;
double value;
}
{
value = Term()
(
<PLUS>
i = Term()
{ value += i;}
|
<MINUS>
i = Term()
{ value -= i; }
)*
{ return value; }
}
double Term() throws NumberFormatException :
{
double i;
double value;
}
{
value = Primary()
(
<TIMES>
i = Primary ()
{ value *= i;}
|
<DIVIDE>
i = Primary ()
{ value /= i; }
)*
{ return value; }
}
double Primary() throws NumberFormatException :
{
Token t;
double d;
}
{
t = <NUMBER>
{ return Double.parseDouble( t.image ); }
|
<PREVIOUS>
{ return previousValue; }
|
<OPEN_PAR> d = Expression() <CLOSE_PAR>
{ return d; }
|
<MINUS> d = Primary()
{ return -d; }
}