一、了解BNF范式
巴科斯范式(BNF: Backus-Naur Form 的缩写)是由 John Backus 和 Peter Naur 首先引入的用来描述计算机语言语法的符号集。
现在,几乎每一位新编程语言书籍的作者都使用巴科斯范式来定义编程语言的语法规则。
巴科斯范式的内容
在双引号中的字("word")代表着这些字符本身。而double_quote用来代表双引号。
在双引号外的字(有可能有下划线)代表着语法部分。
尖括号( < > )内包含的为必选项。
方括号( [ ] )内包含的为可选项。
大括号( { } )内包含的为可重复0至无数次的项。
竖线( | )表示在其左右两边任选一项,相当于"OR"的意思。
::= 是“被定义为”的意思。
二、分析语法,由于采用递归下降分析,因此先对文法进行分析改变,消除左递归和提取左因子,并对之前的文法进行完善,得到文法
三、对文法进行分析实现
1、创建TreeNode类,里面内嵌许多固定类型,具体设计如下
//program子树用于记录整个程序,所有语句为其孩子节点
public static final int PROGRAM = 0;
//存储变量,value为TOK.ID类型的token,叶节点。
public static final int IDNODE= 1;
//常量节点,value为相应的token类型,叶结点
public static final int CONSTANTNODE= 2;
//value为TOK.BREAK类型的token,叶结点
public static final int BREAKNODE = 3;
//类型节点,value值为类型,孩子节点为数组大小
public static final int TYPENODE= 4;
//声明变量的子树,第一个孩子为TYPENODE,第二个为IDNODE,第三个如果有的话是被赋的值,为expr子树
public static final int DECLARENODE = 5;
//带括号的stmt子树,里面有0到多个stmt,为其children
public static final int STMTBLOCKNODE = 6;
/* 记录if子树,第一个孩子为expr子树,为判断条件,第二个为条件为真的时的语句,stmtblock子树
第三个为条件为假的语句stmtblock子树 */
public static final int IFNODE = 7;
//第一个孩子为expr子树,为判断条件,第二个为循环体,stmtblock子树
public static final int WHILENODE = 8;
//记录赋值语句,第一个孩子结点为value子树,第二个为expr子树
public static final int ASSIGNNODE= 9;
//第一个孩子节点为varNode,其余孩子节点为expr子树,为数组下标
public static final int VALUENODE = 10;
//仅一个子树,表示要读的VALUENODE
public static final int READNODE = 11;
//仅一个子树,表示要读的expr子树
public static final int WRITENODE = 12;
//表示有操作数的子树,value为相应的操作符token,第一个孩子为左操作数,第二个为右操作数
public static final int OPTNODE = 13;
//单目运算符负号,子树为取反的值
public static final int NEGATIVENODE= 14;
private ArrayList<TreeNode> children;//孩子节点数组
private int type;//子树类型
private Token value;//子树的值
private int priority;//操作符优先级
2、根据first集和follow集进行预测分析,
先对program进行解析,所有的语句都是其孩子节点
public void progarm()throws UnexpectedException{
//program里面的children里面记录了所有的stmt子树
programNode.getChildren().add(stmt());
while (token.getType()!=TOK.EOF){
programNode.getChildren().add(stmt());
}
}
Expr类型进行解析,通过符号栈和操作数栈,根据优先级建立抽象语法树
Getexpr函数用于取得操作数,实现 Expr --> - Expr E | Value E | Constant E | (Expr) E 这句文法,
E函数用于取得操作符,实现 E --> + Expr E | – Expr E | * Expr E | / Expr E | % Expr E| <= Expr E|< Expr E | > Expr E | >= Expr E | != Expr E | == Expr E|null 这句文法
Expr()函数主要是在最后进行一个栈的计算,完成最后的抽象语法树建立
在建立抽象语法树的时候利用的中缀转后缀的方式,各种节点的优先级定义在Proprity类中。
private void getexpr(Stack<TreeNode> optStack,Stack<TreeNode> numStack,int lparentNum)throws UnexpectedException{
if(token.getType()==TOK.ID){
numStack.push(value());//获得值子树,将操作数进栈
}else if(token.getType()==TOK.LPARENT) {
TreeNode lparent=new TreeNode(TreeNode.OPTNODE);
lparent.setPriority(Priority.LPARENT);
lparent.setValue(token);
match(TOK.LPARENT);
optStack.push(lparent);
lparentNum++;
getexpr(optStack,numStack,lparentNum);
if(token.getType()==TOK.RPARENT) {
if (lparentNum != 0) {
TreeNode optNode = new TreeNode(TreeNode.OPTNODE);//创建操作符子树
optNode.setValue(token);
optNode.setPriority(Priority.RPARENT);
match(TOK.RPARENT);
while (optStack.peek().getValue().getType() != TOK.LPARENT) {
TreeNode opt = optStack.pop();//符号栈出栈
if (opt.getType() != TreeNode.NEGATIVENODE) {//如果是二元操作数
TreeNode right = numStack.pop();
opt.getChildren().add(numStack.pop());//设置左操作数
opt.getChildren().add(right);//设置右操作数
} else {
opt.getChildren().add(numStack.pop());//一元运算符只有一个操作数
}
numStack.push(opt);
}
optStack.pop();//弹出左括号
lparentNum--;
}
}
}else if(token.getType()==TOK.MINUS){
TreeNode negative=new TreeNode(TreeNode.NEGATIVENODE);
negative.setPriority(Priority.NEGATIVE);
optStack.push(negative);
match(TOK.MINUS);
getexpr(optStack,numStack,lparentNum);
}else{
numStack.push(constant());//获得常数子树,并进栈
}
E(optStack,numStack,lparentNum);//进一步添加操作数的value和右操作数
}
//设置操作符的value和右操作符
private void E(Stack<TreeNode> optStack,Stack<TreeNode> numStack ,int lparentNum) throws UnexpectedException {
//expr的follow集为; ] )
if (token.getType() == TOK.SEMI | token.getType() == TOK.RBRACKET|token.getType() == TOK.RPARENT) {
//为终结符不做任何操作,等待返回
}else if(token.getType() == TOK.PLUS|token.getType() == TOK.MINUS|token.getType() == TOK.MUL
|token.getType() == TOK.DIV|token.getType() == TOK.PERSENT|token.getType() == TOK.LQT
|token.getType() == TOK.LT|token.getType() == TOK.GQT|token.getType() == TOK.GT
|token.getType() == TOK.EQ|token.getType() == TOK.NEQ) {
TreeNode optNode = new TreeNode(TreeNode.OPTNODE);//创建操作符子树
optNode.setValue(token);
switch (token.getType()) {
case TOK.PLUS:
optNode.setPriority(Priority.PLUS);//设置优先级
match(TOK.PLUS);
break;
case TOK.MINUS:
optNode.setPriority(Priority.MINUS);
match(TOK.MINUS);
break;
case TOK.MUL:
optNode.setPriority(Priority.MUL);
match(TOK.MUL);
break;
case TOK.DIV:
optNode.setPriority(Priority.DIV);
match(TOK.DIV);
break;
case TOK.PERSENT:
optNode.setPriority(Priority.PERSENT);
match(TOK.PERSENT);
break;
case TOK.LQT:
optNode.setPriority(Priority.LQT);
match(TOK.LQT);
break;
case TOK.LT:
optNode.setPriority(Priority.LT);
match(TOK.LT);
break;
case TOK.GQT:
optNode.setPriority(Priority.GQT);
match(TOK.GQT);
break;
case TOK.GT:
optNode.setPriority(Priority.GT);
match(TOK.GT);
break;
case TOK.EQ:
optNode.setPriority(Priority.EQ);
match(TOK.EQ);
break;
case TOK.NEQ:
optNode.setPriority(Priority.NEQ);
match(TOK.NEQ);
break;
}
while (optStack.size() != 0 && optStack.peek().getPriority() <= optNode.getPriority()) {
TreeNode tempNode = optStack.pop();//弹出运算符
if (tempNode.getType() == TreeNode.NEGATIVENODE) {
tempNode.getChildren().add(numStack.pop());
} else {
TreeNode right = numStack.pop();//弹出右节点
tempNode.getChildren().add(numStack.pop());//弹出并添加左节点
tempNode.getChildren().add(right);//添加右节点
}
numStack.push(tempNode);//将新的子树压入操作数栈
}
optStack.push(optNode);//优先级大的时候直接进栈
getexpr(optStack,numStack,lparentNum);//继续添加操作数子树
}else {
//不是操作符或者终结符,获取操作数
}
}
private TreeNode expr()throws UnexpectedException{
Stack<TreeNode> optStack=new Stack<>();//符号栈
Stack<TreeNode> numStack=new Stack<>();//操作数栈
int lparentNum=0;//左括号的数目
//先调用操作符进栈,将负号和括号进栈
getexpr(optStack,numStack,lparentNum);
while (optStack.size()!=0){
//当只有负数的时候
if(optStack.peek().getType()==TreeNode.NEGATIVENODE){
TreeNode temp=optStack.pop();
temp.getChildren().add(numStack.pop());
numStack.push(temp);
}else{//双目运算符的时候
TreeNode temp=optStack.pop();
TreeNode right=numStack.pop();
temp.getChildren().add(numStack.pop());
temp.getChildren().add(right);
numStack.push(temp);
}
}
if(numStack.size()==1){
return numStack.pop();
}else{
throw new UnexpectedException(numStack.pop().getValue(),"表达式出现错误");
}
}
3,解析变量声明语句
将每个文法实现为一个函数,在varDecl函数中建立varDecl节点,type函数得到的值设置为varDecl节点的第一个孩子,为声明的类型,varList函数得到声明的变量节点,使用curNode和fatherNode实现当一行声明多个变量的时候,创建多个varDecl节点,将其添加在父节点上。
//分析类型节点
private TreeNode type()throws UnexpectedException{
TreeNode typeNode=new TreeNode(TreeNode.TYPENODE);
if(token.getType()==TOK.INT){
typeNode.setValue(token); //声明为int型的子树
match(TOK.INT);
//判断时候为数组并设置数组长度
T(typeNode);
}else if(token.getType()==TOK.DOUBLE){
typeNode.setValue(token);//声明为double型的子树
match(TOK.DOUBLE);
//判断时候为数组并设置数组长度
T(typeNode);
}else {
throw new UnexpectedException(token,"错误类型,需要正确数据类型");
}
return typeNode;
}
//声明变量表子树
private void varList(TreeNode fatherNode)throws UnexpectedException{
TreeNode varNode=new TreeNode(TreeNode.IDNODE);//声明变量类型子树
varNode.setValue(token);//value值为ID类型token
curNode.getChildren().add(varNode);
match(TOK.ID);
//如果声明时发生赋值
if(token.getType()==TOK.ASSIGN){
match(TOK.ASSIGN);
curNode.getChildren().add(expr());
}
//当一行声明多个多个变量的时候创建多个DECLARENODE添加到父节点上
while (token.getType()==TOK.COMMA){
match(TOK.COMMA);
TreeNode newDeclNode=new TreeNode(TreeNode.DECLARENODE);
//将当前的类型赋给同时声明的变量
newDeclNode.getChildren().add((TreeNode) curNode.getChildren().get(0).clone());
TreeNode moreVar=new TreeNode(TreeNode.IDNODE);
moreVar.setValue(token);
newDeclNode.getChildren().add(moreVar);
match(TOK.ID);
//如果声明时发生赋值
if(token.getType()==TOK.ASSIGN){
match(TOK.ASSIGN);
curNode.getChildren().add(expr());
}
fatherNode.getChildren().add(newDeclNode);//将多个声明的节点添加到父节点上
}
}
//变量声明
private TreeNode varDecl(TreeNode fatherNode)throws UnexpectedException{
//创建变量声明子树
TreeNode varDecl=new TreeNode(TreeNode.DECLARENODE);
curNode=varDecl;//变更当前处理的子树
varDecl.getChildren().add(type());//添加相应数据类型的数
varList(fatherNode);//将做子树作为声明的变量中
if(token.getType()==TOK.SEMI){
match(TOK.SEMI);
}else {
throw new UnexpectedException(token,"未正确结尾,需要‘;’");
}
return varDecl;
}
这样建立的声明数的一个缺点是第一个声明的位置在孩子数组的最后一个,其余位置和声明顺序相同
4、其余语句比较简单,依照递归下降法进行语法分析及添加节点就好,具体分析可以看代码注释