一、语义分析要解决的问题
•确定类型:确定标识符所关联的数据对象的数据类型。
•类型检查:按照语言的类型规则,对运算及运算分量进行类型检查,必要时做出相应类型转换。
•识别含义:根据程序设计语言的语义定义,确定各个构造部分组合后的含义,做出相应处理(生成中间代码或者目标代码)。
•静态语义检查:比如控制流检查,嵌套层数检查。
二、总体思路说明
之前在语义分析阶段生成了抽象语法树,抽象语法树本身就是一种中间代码,因此现在将语义分析和解释执行结合在一起,对抽象语法树进行一次遍历,完成语义分析、生成符号表并输出最后结果。抽象语法树的遍历类似于属性文法树的遍历。
文法规定:
l 所有变量声明是默认为int类型,int类型默认值为0,double为0.0。
l 除了数组变量外,其余变量在声明是都可与赋值
l 在声明数组时,可以生成多维数组,维数没有限制,声明数组的形式为int[m][n] a; m和n都为常数
l 数组在声明后,默认每个值都为0或者0.0
l 赋值时,int类型可以自动转成double,double类型不能转换为int,true转换为int和double是默认为0或0.0,false默认为1或者1.0。
l 整数和实数都支持16进制类型数 如-0x14ae.123;
l If和while语句中,如果类型为true或者值不为0.0或者0都视为满足条件
l Break跳出最近的循环
l Read语句用于从命令读取数值赋值给变量
l Write语句用于输出expr的值,可以是变量、常数或者和表达式
三、实现代码(代码比较长为了不破坏结构将代码部分字体放小,可以放大看)
1、之前代码完善
之前在声明变量时,如果用int a,b,c;声明语句时,生成语法树的顺序会发生改变,对语义分析会产生影响,因此对varDeclare部分进行修改,使顺序正确。
2、主要类型说明
在实现过程中,新建了三个类:
l Record类:记录着声明的变量的信息
l symbolTable类:符号表,里面有两个表,一个是table表,记录所有的声明的变量,一个是arrayTable,记录所有数组的声明。符号表中存储着Record。符号表的结构是arrarList,而每个内容是record,record里面有一个next属性指向同名的前一个record,内层的声明的变量会取代外层声明的,类似于杂凑表结构。
l Value类:返回表达式计算的结果,包括值和类型
类结构如下
public class Record implements Cloneable {
public static final int intType=0;//数据类型
public static final int doubleType=1;
Token name;//变量名,记录为tokem便于以后报错
ArrayList<Integer> arrayNum;//数组类型变量的维度大小
private int type;
private int intValue;
private double doubleValue;
private int level;//变量的层数
private Record next;//链表指针
public class SymbolTable {
private ArrayList<Record> table;//普通变量
private ArrayList<Record> arraytable;//数组变量
public class Value {
public static final int intType=0;
public static final int doubleType=1;
public static final int trueType=2;
public static final int falseType=3;
private int type;//类型
private int intValue;//int型值
private double doubleValue;//double型值
3、program节点
创建executeProgram函数,初始化level,遍历指向每一个子树,对其调用executeStmt,执行完毕后清除level为0的符号表,并删除整个符号表
//执行executeProgram节点
public void executeProgram(TreeNode program)throws UnexpectedException{
int level=0;//运算的层数
for(TreeNode node :program.getChildren()){
executeStmt(node,level);
}
//倒着删除声明的数目的变量
symbolTable.deleteRecord(level);
//删除整个table
symbolTable.clearTable();
}
4、stmt节点
创建executeStmt,根据treeNode的不同,调用不同的执行函数
//根据treeNode的不同,调用不同的执行函数
public void executeStmt(TreeNode stmtNode,int level)throws UnexpectedException{
switch ( stmtNode.getType()) {
case TreeNode.DECLARENODE:
executeDeclare(stmtNode, level);
break;
case TreeNode.ASSIGNNODE:
executeAssign(stmtNode, level);
break;
case TreeNode.IFNODE:
executeIf(stmtNode,level);
break;
case TreeNode.WHILENODE:
executeWhile(stmtNode,level);
break;
case TreeNode.STMTBLOCKNODE:
executeStmtBlock(stmtNode, level);
break;
case TreeNode.WRITENODE:
executeWrite(stmtNode, level);
break;
case TreeNode.READNODE:
executeRead(stmtNode,level);
break;
case TreeNode.BREAKNODE:
//是program的break,什么都不做
break;
}
}
5、stmtBlock节点
创建executeStmtBlock函数,对于每一个stmtBlock,代码都新进一层,因此level+1。对其所有的孩子节点,先判断是否出现过了break语句,如果出现则跳出stmtBlock,否则调用stmtBlock。最后删除相应level的记录
//执行stmtBlock,层数递增
public void executeStmtBlock(TreeNode stmtBlock ,int level)throws UnexpectedException{
level++;//层数递增
for(TreeNode node : stmtBlock.getChildren() ){
//判断是否是breakj节点或者前面出现了break节点
if(node.getType()==TreeNode.BREAKNODE||hasBreak==true){
if(whileNum!=0){
hasBreak=true;
break;//有break不断跳出
}
}else {
executeStmt(node,level);//没有break继续执行
}
}
symbolTable.deleteRecord(level);//删除符号表
}
6、Value节点
创建executeValue函数,实现执行Value节点。从符号表中取得相应的记录。对于一般变量,如果在符号表中存在,则返回相应的Record,如果不存在则报错。对于数组变量,先在数组表中查找中是否有该数组并检查是否越界,如果存在且没有越界,则查看在table表中有没有一模一样的变量,如果有则返回该变量,如果没有则在table表中创建一个并返回。
//执行value节点,返回相应的符号表记录
public Record executeValue(TreeNode tempChild,int level)throws UnexpectedException{
Record tempRecord;
if(tempChild.getChildren().size()==1){
//如果只是普通变量,直接搜索
tempRecord=symbolTable.searchRecord(tempChild.getChildren().get(0).getValue());
}else{
//如果是数组变量
ArrayList<Integer> nums=new ArrayList<>();
Value iNum;
//得到下标数组
for(int i=1;i<tempChild.getChildren().size();i++){
iNum=executeExpr(tempChild.getChildren().get(i),level);//计算数组的下标
//如果类型下标类型不为int
if(iNum.getType()!=Value.intType){
throw new UnexpectedException(tempChild.getChildren().get(0).getValue(),"数组下标不为整数");
}else{
nums.add(iNum.getIntValue());
}
}
//搜索数组
tempRecord=symbolTable.searchArrayRecord(tempChild.getChildren().get(0).getValue(),nums);
}
if(tempRecord==null){
throw new UnexpectedException(tempChild.getChildren().get(0).getValue(),"未声明标识符!");
}
return tempRecord;
}
7、expr节点
计算expr节点的值,如果是constant节点直接返回记录信息的value,如果是option节点或者negative,则进行计算。当节点类型为16进制是要进行转换,尤其是16进制小数
//执行expr
public Value executeExpr(TreeNode tempChild,int level)throws UnexpectedException{
Value value=new Value();
//如果是操作符节点,递归变量并返回运算后的值
if(tempChild.getType()==TreeNode.OPTNODE||tempChild.getType()==TreeNode.NEGATIVENODE){
Value op1,op2;
//如果是双目运算符
if(tempChild.getType()==TreeNode.OPTNODE){
op1=executeExpr(tempChild.getChildren().get(0),level);
op2=executeExpr(tempChild.getChildren().get(1),level);
return executeOption(tempChild.getValue(),op1,op2);
}else {
//如果是单目运算符
op1=executeExpr(tempChild.getChildren().get(0),level);
value=executeNegative(op1);
}
}else {
//如果是操作数节点,如果是常数类型则直接返回值,如果是value类型则在表中查找返回值,如果没有找的则报错
if (tempChild.getType() == TreeNode.CONSTANTNODE) {
if (tempChild.getValue().getType() == TOK.LITERAL_INT) {
value.setType(Value.intType);
String num=tempChild.getValue().getValue();
//根据是否是16进制进行转换
if(num.contains("x")||num.contains("X")){
num=num.replace("0x","");
num=num.replace("0X","");
value.setIntValue(Integer.parseInt(num,16));
}else{
value.setIntValue(Integer.parseInt(num));
}
} else if(tempChild.getValue().getType()==TOK.LITERAL_DOUBLE) {
value.setType(Value.doubleType);
String num=tempChild.getValue().getValue();
//根据是否是16进制进行转换
if(num.contains("x")||num.contains("X")){
num=num.replace("0x","");
num=num.replace("0X","");
value.setDoubleValue(hexToDouble(num));
}else{
value.setDoubleValue(Double.parseDouble(num));
}
}else if(tempChild.getValue().getType()==TOK.TRUE){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
} else {
//如果是value节点
Record tempRecord = executeValue(tempChild, level);
//对Value进行赋值
if(tempRecord.getType()==Record.intType){
value.setType(Value.intType);
value.setIntValue(tempRecord.getIntValue());
}else{
value.setType(Value.doubleType);
value.setDoubleValue(tempRecord.getDoubleValue());
}
}
}
return value;
}
8、option节点
根据option的类型对两个value进行相应的计算,如果两个操作数中,有一个为double,则最后的类型为double类型
//执行option
public Value executeOption( Token optToken,Value op1,Value op2)throws UnexpectedException{
Value value=new Value();
int type;
//确定最后计算结果的类型,有一个为double则最后结果为double
if(op1.getType()==Value.doubleType||op2.getType()==Value.doubleType){
type=Value.doubleType;
}else {
type=Value.intType;
}
value.setType(type);
//对两个操作符进行运算
switch (optToken.getType()){
case TOK.PLUS:
if(type==Value.intType){
value.setIntValue(op1.getIntValue()+op2.getIntValue());
}else {
//如果都不是整数则全部转换为double类型
value.setDoubleValue(op1.getCastValue()+op2.getCastValue());
}
break;
case TOK.MINUS:
if(type==Value.intType){
value.setIntValue(op1.getIntValue()-op2.getIntValue());
}else {
//如果都不是整数则全部转换为double类型
value.setDoubleValue(op1.getCastValue()-op2.getCastValue());
}
break;
case TOK.MUL:
if(type==Value.intType){
value.setIntValue(op1.getIntValue()*op2.getIntValue());
}else {
//如果都不是整数则全部转换为double类型
value.setDoubleValue(op1.getCastValue()*op2.getCastValue());
}
break;
case TOK.DIV:
//检查除零错误
if(op2.getCastValue()==0.0){
throw new UnexpectedException(optToken,"除数不能为0");
}
if(type==Value.intType){
value.setIntValue(op1.getIntValue()/op2.getIntValue());
}else {
//如果都不是整数则全部转换为double类型
value.setDoubleValue(op1.getCastValue()/op2.getCastValue());
}
break;
case TOK.PERSENT:
if(op2.getCastValue()==0.0){
throw new UnexpectedException(optToken,"余数不能为0");
}
if(op1.getType()==Value.doubleType||op2.getType()==Value.doubleType){
value.setDoubleValue(0.0);
}else{
value.setIntValue(op1.getIntValue()-op1.getIntValue()/op2.getIntValue()*op2.getIntValue());
}
case TOK.GT:
if(op1.getCastValue()>op2.getCastValue()){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
break;
case TOK.LT:
if(op1.getCastValue()<op2.getCastValue()){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
break;
case TOK.EQ:
if(op1.getCastValue()==op2.getCastValue()){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
break;
case TOK.NEQ:
if(op1.getCastValue()!=op2.getCastValue()){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
break;
case TOK.GQT:
if(op1.getCastValue()>=op2.getCastValue()){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
break;
case TOK.LQT:
if(op1.getCastValue()<=op2.getCastValue()){
value.setType(Value.trueType);
}else{
value.setType(Value.falseType);
}
break;
}
return value;
}
9、negative节点
对value的值进行取反
//对操作数进行取反
public Value executeNegative(Value value){
if(value.getType()==Value.intType){
value.setIntValue(-value.getIntValue());
}else{
value.setDoubleValue(-value.getDoubleValue());
}
return value;
}
10、declare节点
向符号表中添加记录,如果变量为一般变量,直接添加到table中,如果是数组变量,则添加到arrayTable中,并将数组大小记录在record的arrayNum中。如果在声明的时候进行赋值就计算expr结果,进行类型转换
//执行变量声明语句
public void executeDeclare(TreeNode tempChild,int level)throws UnexpectedException{
//添加记录
Record newRecord=new Record();
//设置层级
newRecord.setLevel(level);
//设置类型
if( tempChild.getChildren().get(0).getValue().getType()== TOK.INT){
newRecord.setType(Record.intType);
}else {
newRecord.setType(Record.doubleType);
}
//设置变量名
newRecord.setName(tempChild.getChildren().get(1).getValue());
//判断是否是数组,然后添加到不同的表中
if( tempChild.getChildren().get(0).getChildren().size()>0){
for(int j=0;j<tempChild.getChildren().get(0).getChildren().size();j++){
newRecord.getArrayNum().add(
Integer.parseInt(tempChild.getChildren().get(0).getChildren().get(j).getValue().getValue()));
}
symbolTable.addArrayRecord(newRecord);
}else{
symbolTable.addRecord(newRecord);
}
//如果在声明时赋值则设置value
if(tempChild.getChildren().size()==3){
Value result = executeExpr(tempChild.getChildren().get(2), level);
//如果类型相等,直接更新值
if (newRecord.getType() == result.getType()) {
if (result.getType() == Value.intType) {
newRecord.setIntValue(result.getIntValue());
} else {
newRecord.setDoubleValue(result.getDoubleValue());
}
} else if (newRecord.getType() == Record.intType && result.getType() == Value.doubleType) {
//如果类型不等,进行类型准换或者报错
throw new UnexpectedException(tempChild.getChildren().get(1).getValue(),
"类型不兼容,double转int会损失精度");
} else if(newRecord.getType() == Record.doubleType && result.getType() == Value.intType){
newRecord.setDoubleValue(result.getIntValue());
}else if(newRecord.getType()==Record.intType&&result.getType()==Value.trueType){
newRecord.setIntValue(1);
}else if(newRecord.getType()==Record.doubleType&&result.getType()==Value.trueType){
newRecord.setDoubleValue(1.0);
}else{
//false类型准换为int或者double是默认为0,不发生变化
}
}
}
11、assign节点
创建executeAssign函数,执行赋值语句。先获取符号表中的变量Record,再计算出expr的值,进行类型比较和类型转换
//执行变量赋值语句
public void executeAssign(TreeNode tempChild,int level)throws UnexpectedException{
Record tempRecord=executeValue(tempChild.getChildren().get(0),level);
//如果找到则更新变量值
if(tempRecord!=null) {
Value result = executeExpr(tempChild.getChildren().get(1), level);
//如果类型相等,直接更新值
if (tempRecord.getType() == result.getType()) {
if (result.getType() == Value.intType) {
tempRecord.setIntValue(result.getIntValue());
} else {
tempRecord.setDoubleValue(result.getDoubleValue());
}
} else if (tempRecord.getType() == Record.intType && result.getType() == Value.doubleType) {
//如果类型不等,进行类型准换或者报错
throw new UnexpectedException(tempChild.getChildren().get(0).getChildren().get(0).getValue(),
"类型不兼容,double转int会损失精度");
} else if(tempRecord.getType() == Record.doubleType && result.getType() == Value.intType){
tempRecord.setDoubleValue(result.getIntValue());
}else if(tempRecord.getType()==Record.intType&&result.getType()==Value.trueType){
tempRecord.setIntValue(1);
}else if(tempRecord.getType()==Record.doubleType&&result.getType()==Value.trueType){
tempRecord.setDoubleValue(1.0);
}else{
//false类型准换为int或者double是默认为0,不发生变化
}
}
}
12、read节点
从符号表中取得Record并更新值
public void executeRead(TreeNode readNode,int level)throws UnexpectedException{
//从命令行读取数据
Scanner scanner=new Scanner(System.in);
Record record=executeValue(readNode.getChildren().get(0),level);//从符号表中取得记录
if(record.getType()==Record.intType){
record.setIntValue(scanner.nextInt());
}else{
record.setDoubleValue(scanner.nextDouble());
}
}
13、write节点
创建executeWrite,将其中的expr的值打印出来
//执行write语句
public void executeWrite(TreeNode writeNode,int level)throws UnexpectedException{
//输出表达式的值
Value value=executeExpr(writeNode.getChildren().get(0),level);
if(value.getType()==Value.intType){
System.out.println(value.getIntValue());
}else if(value.getType()==Value.doubleType){
System.out.println(value.getDoubleValue());
}else if(value.getType()==Value.trueType){
System.out.println(true);
}else{
System.out.println(false);
}
}
14、if节点
if语句,当条件为真的时候执行真的语句,条件为假的时候执行假的语句
//执行if语句
public void executeIf(TreeNode ifNode,int level)throws UnexpectedException{
Value condition=executeExpr(ifNode.getChildren().get(0),level);
//true和非0值使条件为真
if(condition.getType()==Value.trueType||condition.getCastValue()!=0.0){
executeStmtBlock(ifNode.getChildren().get(1),level);
}else{
if(ifNode.getChildren().size()>2){
executeStmtBlock(ifNode.getChildren().get(2),level);
}
}
}
15、while节点
添加一个全局变量whileNum栈,遇到一个进入一个while节点就加一,添加全局变量hasBreak,记录当前是否遇到break。
在condition为真时执行循环体,每执行一次判断是否遇到了break,如果遇到了就跳出,否则检查condition,继续循环。当执行完循环体的时候,将hasBreak设置为false并将whileNum-1。
public void executeWhile(TreeNode whileNode,int level) throws UnexpectedException{
Value condition=executeExpr(whileNode.getChildren().get(0),level);
whileNum++;
while (condition.getType()==Value.trueType||condition.getCastValue()!=0.0){
executeStmtBlock(whileNode.getChildren().get(1),level);
if(hasBreak==true){
break;
}
condition=executeExpr(whileNode.getChildren().get(0),level);
}
hasBreak=false;
whileNum--;
}
16、Break节点
创建hasBreak变量,当在stmtBlock中遇到break或者hasBreak为true时,如果whileNum不为0,则将hasBreak置为true并跳出,这样就能一直返回到while语句中,再最后跳出。