使用c/c++实现语义分析器

一、前言

  这次编译原理实验要求实现语义分析,但最近空闲时间比较少,只能快速过一遍基础知识硬着头皮写了。但由于之前我设计文法大多只是为了测试使用,所以文法很不规范,这直接导致了语义子程序十分难实现,所以要想实现一个好的语义分析器,必须先设计好你的文法。
  需要注意的是,这个语义分析器是建立在我之前写的语法分析器之上的。

二、具体实现

  由于之前语法分析实现的是SLR(1)语法分析器,是一种自底向上的分析方法,所以这里我们使用语法制导翻译,即在进行语法分析的过程中(在归约的时候)实现语义分析。
  为了实现语义分析,我们需要将归约的字符串与一系列的动作关联起来,也就是,将有语义分析意义的产生式和语义子程序关联起来,每当使用这个产生式进行归约的时候,调用对应的语义子程序对符号的属性(这里我只用到了综合属性,即该属性的值只通过该语法树中该符号对应节点的孩子节点的属性)进行计算。
  先给出文法,需要注意的是,这个文法是有问题的!只能简单用用,不要深究。
在这里插入图片描述

1、需要修改或添加的结构体

intermediate_code.h

  这个头文件声明了中间代码的结构体,lineNum是中间代码的编号,从100开始;code是生成的三地址码。
  如100: a = b + 4,100对应的lineNum,a = b + 4对应的code。
在这里插入图片描述

symbol_line.h

  这个是符号表单元,这里我简化了结构,仅仅用作变量查重(重复声明)和类型冲突(如boolean和int不能做运算)。
  addr表示变量的名称,value表示变量的值,type表示变量的类型。比如一个int类型的变量a的值是4,则addr是a,value是4,type是int。
  需要注意的是,我这里也将临时变量存入符号表了,而且value的值是有bug的,不过没时间改了,需要的话可以自己改改。
在这里插入图片描述

word.h

  因为语法分析的时候需要一些属性,所以在词法分析后生成的符号串中,要保存一些属性。这里相比之前添加了addr、extra和variable。code没用,key存的是关键字,比如int、while之类的,但要注意的是,对于变量,key存的是id,对于数字,key存的是num。
  当字符是数字的时候,addr存的是该数字的值,如12;当字符是变量的时候,addr存的是变量名,如a。
  当字符是数值类型的时候(int、double和boolean),extra存的就是对应类型;当字符是运算符的时候(+、-、*、/),extra存的就是对应的运算符。
  variable没有用到,可以忽略。
在这里插入图片描述

2、重要结构介绍

int tempNum = 0;  // 临时变量的序号

int lineNum = 100;  // 中间代码序号

// 中间代码
IntermediateCodeLine lines[1000];  // 中间代码行数组
int lineTop = 0;  // 数组尾

// 符号表
SymbolLine symbolTable[1000];  // 符号表
int tableTop = 0;  // 符号表尾

// 循环栈
// 栈顶记录的是最内层的循环开始时的中间代码在数组中的下标
int loopStack[100];  // 循环栈
int loopStackTop = 0;  // 循环对应的中间代码下标

  这里中间代码数组和符号表没什么好说的,需要注意的是循环栈,栈顶记录的是当前最内层循环开始对应的中间代码在数组中的下标,我们遇到while的时候需要先把此时到达的中间代码行空下,等到将while的整个结构归约的时候再填上。因为刚归约到while的时候,我们是不知道while循环结束的条件(还没归约到)和要跳转的中间代码行序号(因为while循环体中的代码还没归约到)。
  举个例子,下面是我定义的循环的产生式:
在这里插入图片描述
  W就是while归约成的,C是条件,S是循环体中的代码。归约到while的时候我们就需要准备中间代码中的跳转语句了(goto),但此时后面的C和S还没有归约到,所以我们不知道循环结束的条件是什么,也不知道S中会产生几条中间代码,也就意味着我们不知道要跳转的中间代码的序号是多少。所以在归约到while的时候,需要空出一行来写跳转的中间代码,等到S->W( C ){ S }归约的时候补上跳转的语句。
在这里插入图片描述

3、重要函数介绍

  实际上没什么重要函数,只需要写几个语义子程序就行。
  先看看语义分析的头文件。

#ifndef __SEMANTICALANALYSIS_H__
#define __SEMANTICALANALYSIS_H__

#include "item.h"
#include "production_rule.h"
#include "state.h"
#include "analysis_table_cell.h"
#include "word.h"
#include "symbol.h"

// 获取一个临时变量
string getTempV();

// 产生中间代码
WORD createCode(int productionId, string leftPart, WORD* wordsInStack, int top);

// 打印中间代码
void printIntermediateCodeLine();

// 打印符号表(现在符号表有问题,所以暂时不用)
void printSymbolTable();

#endif

getTempV()

  这个就是获取一个临时变量的名称。因为在三地址码中,一个表达式能表达出的变量数目有限。如要求d = a + b + c的话,需要改成:
  t0 = b + c
  d = a + t0
  这里t0就是临时变量,在运算比较复杂的情况下,会需要很多临时变量,所以需要一个函数来专门获取。这个函数很好理解。

// 获取一个临时变量
string getTempV(){
    return "sys_temp" + to_string(tempNum++);
}

createCode()

  这个函数就是语法分析归约的时候调用的生成中间代码的函数,也很好理解,就是根据归约用的产生式来执行对应的语义子程序。同时是语义分析的入口
在这里插入图片描述

// 语义分析,产生中间代码
WORD createCode(int productionId, string leftPart, WORD* wordsInStack, int top){
    // 封装返回的字符
    WORD result;
    result.key = leftPart;
    result.addr = wordsInStack[top - 1].addr;
    result.extra = wordsInStack[top - 1].extra;

    // 执行语义子程序
    if(productionId == 18){
        SemanticSubroutine_18(wordsInStack[top - 1]);
    }

    if(productionId == 13){
        result = SemanticSubroutine_13(wordsInStack[top - 3], wordsInStack[top - 2], wordsInStack[top - 1], leftPart);
    }

    if(productionId == 19){
        SemanticSubroutine_19(wordsInStack[top - 4], wordsInStack[top - 2], wordsInStack[top - 5]);
    }

    if(productionId == 6){
        SemanticSubroutine_6();
    }

    if(productionId == 1){
        SemanticSubroutine_1(wordsInStack[top - 5]);
    }

    if(productionId == 9){
        result = SemanticSubroutine_9(wordsInStack[top - 3], wordsInStack[top - 2], wordsInStack[top - 1], leftPart);
    }

    return result;

}

checkType()

  这个函数是用来检查两个变量是否能进行运算,比如boolean和int不能进行加减乘除。
  这个用在9. C -> EUE和13. E -> EOE中,即用在9号和13号产生式的语义子程序中,判断两个变量是否能进行比较或者算术运算。并返回兼容的不同类型中更高级的那个(返回后存在对应临时变量的符号表单元中)。

// 检查类型兼容性,并返回更高级的类型
string checkType(string addr1, string addr2){
	// 默认是int类型
    string type1 = "int";
    string type2 = "int";
    // 根据变量名addr1和addr2从符号表查对应变量的类型
    // 如果不在符号表中,那就是数字了,默认是上面初始化的int
    int id1 = getSymbolIdx(addr1);
    if(id1 != -1){
        type1 = symbolTable[id1].type;
    }
    int id2 = getSymbolIdx(addr2);
    if(id2 != -1){
        type2 = symbolTable[id2].type;
    }

    // 如果有一个是bool,就无法运算(因为我这里只定义了int、double和boolean三种类型)
    if(type1.compare("boolean") == 0 || type2.compare("boolean") == 0){
        cout << addr1 << "(" << type1 << ") cant compute with " << addr2 << "(" << type2 << ")..." << endl;
        exit(0);
    }

	// 一般double和int进行计算,都会转换成double类型
    if(type1.compare("double") == 0 || type2.compare("double") == 0){
        return "double";
    } else {
        return "int";
    }
}

SemanticSubroutine_13()

  第13号产生式 E -> EOE对应的语义子程序。就是加段中间代码(x = x + x)。

// 产生式13对应的语义子程序
WORD SemanticSubroutine_13(WORD v1, WORD op, WORD v2, string leftPart){
    // 类型检查
    string type = checkType(v1.addr, v2.addr);

    WORD result;
    // 获取临时变量
    result.addr = getTempV();
    result.key = leftPart;
    // 进行运算检测
    // 参与运算的额数字
    int v1_d = -1;
    int v2_d = -1;
    // 判断v1是否为符号表中的变量,是的话从符号表中取值
    int id = getSymbolIdx(v1.addr);
    if(id == -1){
        v1_d = atoi(v1.addr.c_str());
    } else {
        v1_d = atoi(symbolTable[id].value.c_str());
    }
    // 判断v2是否为符号表中的变量,是的话从符号表中取值
    id = getSymbolIdx(v2.addr);
    if(id == -1){
        v2_d = atoi(v2.addr.c_str());
    } else {
        v2_d = atoi(symbolTable[id].value.c_str());
    }
    
    // 进行符号运算(在extra中保存着运算符号)
    if(op.extra.compare("+") == 0){
        lines[lineTop].code = result.addr + " = " + v1.addr + " + " + v2.addr;
        lines[lineTop].lineNum = lineNum;
        lineTop++;
        lineNum++;
        result.variable = true;
        // 临时变量加入符号表
        symbolTable[tableTop].addr = result.addr;
        symbolTable[tableTop].type = type;
        symbolTable[tableTop].value = v1_d + v2_d;
        tableTop++;
    }

    if(op.extra.compare("-") == 0){
        lines[lineTop].code = result.addr + " = " + v1.addr + " - " + v2.addr;
        lines[lineTop].lineNum = lineNum;
        lineTop++;
        lineNum++;
        result.variable = true;
        // 临时变量加入符号表
        symbolTable[tableTop].addr = result.addr;
        symbolTable[tableTop].type = type;
        symbolTable[tableTop].value = v1_d - v2_d;
        tableTop++;
    }

    if(op.extra.compare("*") == 0){
        lines[lineTop].code = result.addr + " = " + v1.addr + " * " + v2.addr;
        lines[lineTop].lineNum = lineNum;
        lineTop++;
        lineNum++;
        result.variable = true;
        // 临时变量加入符号表
        symbolTable[tableTop].addr = result.addr;
        symbolTable[tableTop].type = type;
        symbolTable[tableTop].value = v1_d * v2_d;
        tableTop++;
    }

    if(op.extra.compare("/") == 0){
        lines[lineTop].code = result.addr + " = " + v1.addr + " / " + v2.addr;
        lines[lineTop].lineNum = lineNum;
        lineTop++;
        lineNum++;
        result.variable = true;
        // 临时变量加入符号表
        symbolTable[tableTop].addr = result.addr;
        symbolTable[tableTop].type = type;
        symbolTable[tableTop].value = v1_d / v2_d;
        tableTop++;
    }

    return result;
}

SemanticSubroutine_19()

  19号产生式,即 E -> TE=E; 对应的语义子程序。主要是生成一条中间代码(x = x)和添加变量至符号表,可以判断是否有变量重复声明。

// 产生式19对应的语义子程序
void SemanticSubroutine_19(WORD v1, WORD v2, WORD type){
    //cout << v2.key << "--" << v1.key << endl;
    // 判断变量是否重复
    if(-1 != getSymbolIdx(v1.addr)){
        cout << "multi declare of variable [" << v1.addr << "]..." << endl;
        exit(0);
    }
    // 将变量添加到符号表
    symbolTable[tableTop].addr = v1.addr;
    // 变量类型(int、double、boolean)保存在extra中
    symbolTable[tableTop].type = type.extra;
    // 变量赋值
    string value;
    int id = getSymbolIdx(v2.addr);
    if(id == -1){
        value = v2.addr;
    } else {
        value = symbolTable[id].value;
    }
    symbolTable[tableTop].value = value;
    tableTop++;

    // 中间代码
    lines[lineTop].code = v1.addr + " = " + v2.addr;
    lines[lineTop].lineNum = lineNum;
    lineTop++;
    lineNum++;
}

SemanticSubroutine_6()

  6号产生式,即W -> while对应的语义子程序,就是上面说过的,空出一行来,等着循环体整体归约的时候补上。

// 产生式6对应的语义子程序
void SemanticSubroutine_6(){
	// 空出一行来等着循环体整体归约的时候补上
    lines[lineTop].code = "blank";
    lines[lineTop].lineNum = lineNum;

    // 记录最内层循环开始的行数
    loopStack[loopStackTop] = lineTop;
    loopStackTop++;
    lineNum++;
    lineTop++;
}

SemanticSubroutine_1()

  1号产生式,即 S -> W©{S}对应的语义子程序,就是上面说过的,补上之前空出的一行,和加一行往回跳转的语句。
在这里插入图片描述

// 产生式1对应的语义子程序
void SemanticSubroutine_1(WORD t){
    // 找到最内层循环开始的行数
    int startLine = loopStack[--loopStackTop];
    // 补上当初空出来的那行条件跳转语句
    lines[startLine].code = "if not " + t.extra + " then goto " + to_string(lineNum + 1);
    //cout << lines[startLine].code << endl;

    // 产生跳回到while判断条件的中间代码
    lines[lineTop].code = "goto " + to_string(lines[startLine].lineNum);
    lines[lineTop].lineNum = lineNum;
    lineTop++;
    lineNum++;
}

SemanticSubroutine_9()

  9号产生式,即C -> EUE对应的语义子程序,主要是判断两个变量是否能比较(是否会产生类型冲突)和记录比较的条件,在补上当初空出来的那行条件跳转语句的时候要用到。

// 产生式9对应的语义子程序
WORD SemanticSubroutine_9(WORD v1, WORD op, WORD v2, string leftPart){
    // 类型检查
    checkType(v1.addr, v2.addr);

    WORD result;
    result.key = leftPart;
    // 记录判断条件,补上当初空出来的那行条件跳转语句的时候要用到
    result.extra = v1.addr + " " + op.extra + " " + v2.addr;
    return result;
}

三、用法

  在main.c文件中修改输入的代码就可以了,因为语义子程序是写死的,所以产生式不能动。
在这里插入图片描述

四、测试

  正常:
在这里插入图片描述

在这里插入图片描述
  重复声明:
在这里插入图片描述
在这里插入图片描述
  运算时类型冲突:
在这里插入图片描述
在这里插入图片描述

五、后记

  实际上这次实现的代码质量非常差,没有足够的时间构建代码的结构,也没有事先写出一个合适的文法来。所以这篇博客比较适合了解思想,而非学习代码内容。希望大家能有所收获,要是有什么问题可以评论一下,谢谢大家的观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值